Bootstrap

Java的基础语法:变量、运算符、注释、流程控制、数组


学习一门语言之前,需要先了解语言的基本的语法结构,例如学习中文前要先了解偏旁部首、拼音。Java是一门编程语言,编程语言的基本结构通常有标识符、注释、变量、运算符、流程控制、存储结构等,Java还是面向对象语言,所以还具备面向对象的基本结构:类、对象、接口。然后Java在发展过程中还衍生了一些高级特性,如:注解、反射、Lambda等。

这里简单介绍Java的基本语法。

一:标识符、注释

标识符

Java对各种变量、方法和类等要素命名时使用的字符序列称为标识符。

定义合法标识符规则:(规则:有强制性,不遵守时编译不通过)

  • 由26个英文字母大小写,0-9,_或$组成
  • 数字不可以开头
  • 不可以使用关键字和保留字,但能包含关键字和保留字
  • Java中严格区分大小写,长度无限制
  • 标识符不能包含空格

注意:Java语言中字母采用的是双字节Unicode 编码1。Unicode叫作统一编码制,它包含了亚 洲文字编码,如中文、日文、韩文等字符。 也就是说:可以用中文、日文、韩文等字符命名,但不建议那样。

命名类名时,不建议和Java官方类库重名。

特殊的标识符: 官方占用的标识符,用来作特殊的作用,我们不能使用。官方占用的标识符分为关键字保留字
关键字

  • 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词),不能挪作他用。
  • 特点:
    • 关键字中所有字母都为小写
    • 关键字的使用支持了Java的语法
  • Java中定义了50个关键字:
    在这里插入图片描述

保留字

  • Java中有一些字符序列既不能当作标识符使用,也不是关键字,也不能在程序中使用,这些字符序列称为保留字。Java语言中的保留字只有两个:goto和 const。

注意:Java中常用的 true、false、null 看起来很像关键字,但其实不是(不在官方文档说明的关键字之中),只是字面量。但在Java中扮演重要角色,有各自的作用,也不能被我们当作标识符使用。

分隔符
在Java源代码中,有一些字符被用作分隔,称为分隔符。分隔符主要有:分号;、左右大括号 {}和空白

  • 分号:
    分号是Java语言中最常用的分隔符,它表示一条语句的结束。
int totals = 1 + 2 + 3 + 4; 

等价于

int totals = 1 + 2 
    			+3 + 4; 
  • 大括号:
    在Java语言中,以左右大括号({})括起来语句集合称为语句块(block)或复合语句,语句块中可以有0~n条语句。在定义类或方法时,语句块也被用做分隔类体或方法体。语句块也可以嵌套,且嵌套层次没有限制。
public class HelloWorld { 

	public static void main(String args[]) { 
		int m = 5; 
		if (m < 10) { 
			System.out.println("<10"); 
		} 
	} 
} 
  • 空白:
    在Java源代码中元素之间允许有空白,空白的数量不限。空白包括空格、制表符(Tab键输入) 和换行符(Enter键输入),适当的空白可以改善对源代码可读性。
if (m < 10) { 
	System.out.println("<10"); } 

等价于 

if (m < 10) 
{
	System.out.println("<10"); 
}

等价于 

if (m < 10) { 
	System.out.println("<10"); 
}

Java的名称命名规范

规范是大家约定俗成的:如果不遵守规范,编译也能通过,但是建议遵守,方便合作。
遵守规范的代码通常会更有利于开发者之间的交流,也更适合在团队中使用。一般我们开发者都有天然的默契就是遵守规范了!

通用的规范如下:(备注:命名尽量见名知义)

  • 项目名:用英文、或英文数字结合。数字不能开头,英文字母全部小写。

  • 包名:用英文、或英文数字结合。数字不能开头,英文字母全部小写。

    • 含Java类的包不能使用特殊字符。可以用字母、数字、下划线,Java package命令要求。

    • 在java开发的过程中,每名Java开发人员都可以编写属于自己的java package,为了在编写中保证每一个java package命名的唯一性,要求开发人员在自己定义的包名前加上唯一的前缀。所以多数开发人员采用自己公司的名称.项目名.模块名…在互联网上的域名称作为自己程序包的唯一前缀(反域名命名规则)。

      • 反域名规则:全部使用小写字母。一级包名为地顶级域名如com,二级包名为xx(可以是公司或则个人的随便),三级包名根据应用进行命名,四级包名为模块名或层级名; 如com.tinyx.myapp.activities;

      个人项目怎么命名呢?

      • indi :

        • 个体项目,指个人发起,但非自己独自完成的项目,可公开或私有项目,版权主要属于发起者。
        • 包名为indi.发起者名.项目名.模块名*…
      • pers :

        • 个人项目,指个人发起,独自完成,可分享的项目,版权主要属于个人。

        • 包名为pers.个人名.项目名.模块名*…

        • priv :

        • 私有项目,指个人发起,独自完成,非公开的私人使用的项目,版权属于个人。

        • 包名为priv.个人名.项目名.模块名*…

        • 另外,我为了区分团队项目和前面所说项目的区分,还有了一下扩展:

      • team :

        • 团队项目指由团队发起,并由该团队开发的项目,版权属于该团队所有。
          包名为team.团队名.项目名.模块名*…
      • com :

        • 公司项目:由项目发起的公司所有。
          包名为com.公司名.项目名.模块名*…
  • 类名、接口名:所有单词的首字母大写(大驼峰命名法):XxxYyyZzz

    • 尽量不要和Java官方类库重名。
  • 变量名、方法名:第一个单词首字母小写,第二个单词开始每个单词首字母大写(小驼峰式命名法):xxxYyyZzz

  • 常量名:所有字母都大写。单词间用下划线隔开。:XXX_YYY_ZZZ

注释

Java中的注释在被编译器编译时会被省略掉。(不会被编译)换句话说,生成的.class文件不包含注释内容(不会影响运行的性能)。

作用: 提高了代码的阅读性、帮助调试所写的代码(运行出错时,可以考虑先把语句注释掉,再运行来找出出错的代码。)

分类:

  • 行注释://...
    //这是行注释
    
  • 多行注释:/*...*/ 注意多行注释里面不允许有多行注释嵌套。
    /*
    这是多行注释
    */
    
  • 文档注释:这是Java特有的,其他语言一般都没有文档注释。
    /**
    这是文档注释
    */
    
    • 特点:注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。

二:变量

什么是变量

变量的概念

  • 是内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化
  • 变量是程序中最基本的存储单元。
  • 包含变量类型变量名存储的值
  • 该区域创建后,变量类型不可变,变量名和存储的值是可变的。
  • 作用:用于在内存中保存数据。

变量的使用语法

/*
》Java创建变量的格式:数据类型 变量名 = 变量值;
 》注意点:
    1.声明和赋值可以分别进行。赋值也可以在声明时进行。
    2.同一种类型的变量可以声明在一起,用逗号隔开。
*/

//声明和赋值同时
String name = "jame";

//变量的声明
int i;
//变量的赋值
i = 1;

//同一种类型的变量可以声明在一起
int i,j,z;
int a = 1, b = 2, c = 3;

//变量的使用:使用变量名来访问这块区域的数据
System.out.println(i);

注意点:

  • 变量定义后:变量类型、变量名不能改,但存储的值可以改。

  • 使用变量名来访问这块区域的数据

  • Java中每个变量必须先声明(声明时就是在创建这个变量),后使用。

  • 变量的作用域:其定义所在的一对{}内

    • 同一个作用域,不能定义重名的变量。(编译不通过)
System.out.println("name");
String name = "jame";
//结果并不会输出 jame,编译报错,因为使用的时候还没有name这个变量。
============================================

int i;
System.out.println(i);
//编译报错,需要初始化

============================================
class Test {
    public static void main(String[] args) {
        System.out.prinltn(name); //编译报错,原因:不在该变量的作用域内。
    }
    
    void method() {
        String name;
        name = "jame";
    }
}

变量的内存解析
Java程序是运行在JVM上的,所有变量的内存由JVM来实现。
首先看看JVM的结构:
在这里插入图片描述
Java字节码文件在JVM上运行时产生以上逻辑区域。值得一提的是,这些结构区域并不是对应硬盘空间的真实区域,而是程序运行时逻辑上形成的虚拟区域。
而我们定义和使用变量时变量的内存逻辑如下图:
在这里插入图片描述
如果变量为基本数据类型,则在栈空间划分空间给变量用来存放指向方法区中数据的引用,方法区则存放变量真实的数据。如果变量为引用数据类型,则在栈空间划分空间给变量用来存放指向堆空间的实例对象的引用,而堆空间的实例对象存放的是指向方法区数据的引用。
如果有些词汇你感到陌生的话,放心继续学习,这些都会在往后的知识点中出现并串联起来。Java知识体系往往是由大到小层层递进的。

变量的分类

总的来说变量可以按三种情况来划分:

1、第一种:按数据类型
在这里插入图片描述
一共8种基本数据类型、3种引用数据类型

它们的区别:

  • 基本数据类型:
    • 名称是固定不变的,是Java官方声明的关键字:int、long、double、short、byte、boolean、char、float
    • 基本数据类型的变量:只能存储数据值。(从方法区中获取)
      • 数据值:是不可变的,也称“常量”。在方法区中。
      • 所以基本数据类型的变量想改变值,只能重新赋一个值。
  • 引用数据类型:
    • 名称是不固定的,是类名、数组类型名、接口名
    • 引用数据类型的变量:只可能存储两类值:null 或 地址值(含变量的类型,指向堆空间的对象)
      • 地址值:指向对象,对象在堆空间中,对象可以改变内部的数据。
      • 所以引用数据类型想改变值,可以重新赋一个值,也可以改变其对象的数据。

2、第二种:按声明位置的不同

  • 在方法体外:成员变量
  • 在方法体内:局部变量
    在这里插入图片描述

类和对象章节会详细介绍

3、第三种:静态变量与非静态变量

  • 实例变量(静态):我们创建类的多个对象,每个对象都独立的拥有一套类中的非静态属性。各个对象的实例变量互不影响。
  • 静态变量(非静态):用标识符 static 修饰,我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一对象修改静态变量的值时,会导致其他对象调用此静态变量时,是修改过了的。

具体也是在类和对象章节介绍

基本数据类型

整型byte、short、int、long

  • 整数类型:byte、short、int、long
  • Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保证java程序的可移植性在这里插入图片描述

说明:

  • Java的整型常量默认为int型。比如:在运算中,如1这样默认是int型的。

  • 定义long型常量必须在后面加 ‘ I ’ 或 ‘ L ’(用大写L可读性更好,小写像数字1)

    Long l = 18L; //加L是声明用的,在真正底层存的时候是没有L的。后面的float等也同理
    Long l2 = 11l;
    //其他类型的声明
    byte b = 1;
    short s = 2;
    int i = 3;
    
  • 编程习惯:Java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long

整数的进制

  • 所有数字在计算机底层都以二进制形式存在。

  • 计算机以二进制补码的形式保存所有的整数

  • Java中对于整数,有四种表示方式:

    • 二进制(binary): 0,1 ,满2进1.以0b或0B开头。
    • 十进制(decimal): 0-9 ,满10进1。
    • 八进制(octal): 0-7 ,满8进1. 以数字0开头表示。
    • 十六进制(hex): 0-9及A-F,满16进1. 以0x或0X开头表示。此处的A-F不区分大小写。如:0x21AF +1= 0X21B0
    int num1 = 0b110;//二进制
    int num2 = 110;//十进制
    int num3 = 0127;//八进制
    int num4 = 0x110A;//十六进制
    
  • Java的 Integer类中,提供了几个静态方法,方便进行进制转换:在这里插入图片描述

浮点型float、double

  • float、double
  • 与整数类型类似,Java浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响

在这里插入图片描述

问题:float、double占用4、8字节,和整型的int、long对应,为什么浮点型的表数范围比整型的表数范围大呢?
答:因为机器上整型的二进制码,和浮点型的二进制码结构不一样。

浮点型常量有两种表示形式:

  • 十进制数形式:如:5.12 512.0f .512 (必须有小数点)
  • 科学计数法形式:如: 5.12E2 512E2 100E-2 ( E? 表示10的?次方)

精度:

  • float:单精度,尾数可以精确到 7位有效数字。很多情况下,精度很难满足需求。
  • double:双精度,精度是float的两倍。通常采用此类型。(但通常商业运算需要更高的精度,double并不能满足)

说明:

  • Java 的浮点型常量默认为double型,也就是说0.0默认是double型。

    float f = 0.0;//编译报错,默认0.0是double型,不能用float接收。
    
  • 声明float型常量,必须后面加 f 或 Ffloat f = 1.1f

事实上,声明double型数据也可以在后面加 d 或 D,以表示是double型数据。但不是必要的。

double的精度问题

public static void main(String[] args) throws Exception {
        System.out.println(0.1+0.2);
        System.out.println(1.0-0.8);
        System.out.println(4.015*100);
        System.out.println(123.3/100);
        double amount1 = 2.15;
        double amount2 = 1.10;
        if (amount1 - amount2 == 1.05)
            System.out.println("OK");
}

结果:
在这里插入图片描述

可以看到,输出结果和我们预期的很不一样。比如,0.1+0.2 输出的不是 0.3 而是0.30000000000000004;

分析:

  • 出现这种问题的主要原因是,计算机是以二进制存储数值的,浮点数也不例外。Java 采用
    了IEEE 754 标准实现浮点数的表达和运算,你可以通过这里查看数值转化为二进制的
    结果。
  • 比如,0.1 的二进制表示为 0.0 0011 0011 0011… (0011 无限循环),再转换为十进制就
    是 0.1000000000000000055511151231257827021181583404541015625。对于计算
    机而言,0.1 无法精确表达,这是浮点数计算造成精度损失的根源。
  • float和double只能用来做科学计算或者是工程计算,它们执行二进制浮点运算,这些运算经过精心设计,能够在广泛的数值范围内提供更精确的快速近似和计算而精心设计的。但是,它们不能提供完全准确的结果,因此不能用于需要计算精确结果的场景中。当浮点数达到一定的大数时自动使用科学计数法。这样的表示只是近似真实数而不等于真实数。当十进制小数转换为二进制时,也会出现无限循环或超出浮点数尾部的长度。在商业计算中我们要用java.math.BigDecimal

字符型char

  • char型数据用来表示通常意义上“字符”:1字符 = 2字节(1字符占用2字节的存储空间)

    Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个数字,一个汉字,一个日文或其他书面语的一个字符。

  • 语法:通常使用一对'',内部只能写一个字符。如 :char c = 'a'

    • 特殊情况:

      char c = 97;//编译通过,对应编码集,但开发中一般不这么写
      System.out.println(c);//a
      
  • 字符型变量的三种表现形式:

    • 字符常量是用单引号括起来的单个字符。如:char c1 = ‘a’
    • Java中还允许使用转义字符 \ 来将其后的字符转变为特殊字符型常量。如:char c2 = ‘\n’
    • 直接使用Unicode值来表示字符型常量。如:’\uXXXX’ 。其中,XXXX代表一个十六进制整数。如:\u000a表示 \n

使用说明:

  • char类型只能存储单个字符。(必须有且只能存储一个字符)

    char c = '';//编译不通过
    char c0 = ' ';//编译通过
    char c1 = 'a'; //编译通过
    char c2 = 'ab'; //编译不通过
    
  • char类型是可以进行运算的。因为它都对应有Unicode

布尔型boolean

  • 只能取两个值之一:true、false

  • 常在判断语句、循环语句中用。

数据类型之间运算规则

这里讨论的7种基本数据类型变量间的运算,是不包含boolean类型的。

通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。

自动类型提升

基本数据类型在参与运算(碰到运算符时),会遵守自动类型提升机制(自动类型转换)(注意不包含boolean类型)。

自动类型提升:基本数据类型中运算规则:容量小的类型自动转换为容量大的类型。

  • 这里容量指的是:表示数字的大小范围
  • 数据类型按容量大小排序为:
    在这里插入图片描述

规则:(不遵守规则:编译不通过)

  • 有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。

  • byte、short、char之间不会互相转换,它们三者之间的运算结果为int类型。

    比如,两个byte类型的数据在进行运算时,运算结果就会变成int类型,如果要结果是byte类型的话,就要强转成byte类型,这是因为java编译器内部对其进行处理。

    特别说明一下,char型的字符会根据编码集转换成对应的数值,并变成int类型参与运算。

  • boolean类型不能与其他数据类型运算

  • 当把任何基本数据类型的值和字符串(String)进行拼接运算(+)时,结果为字符串类型。但一般不这样转换,因为效率低。

byte b1 = 2;
int i1 = 120;
short s1 = 10;
byte b2 = b1 + i1;//编译不通过
byte b3 = b1 + b1;//编译不通过
short s2 = b1 + s1;//编译不通过
int i2 = b1 + i1;//122
long l1 = b1 + i1;//可以用容量大的类型接收容量小的类型的数据
float f = b1 + i1;//122.0
/********************************************/
char c1 = 'a';//97
int i3 = 10;
int i4 = c1 + i3;//107
char c2 = s1 + c1;//编译不通过
Long l = 121212;//编译成功,原因是,121212被视作int型,自动提升为long型
long l1 = 12312313132131;//编译报错:过大的整数。原因:已经超出int的取值范围
long l2 = 12312313132131L;//编译通过

float f = 12.2;//编译失败,12.2默认是double型,不能被较小的float接收。

强制类型转换

  • 作用:强制使用某类型接收某变量。

  • 规则:

    • 对于基本数据类型

      • 自动类型提升运算的逆运算
      • 容量小的类型的变量也可以强转为容量大的类型的变量,只不过有自动提升机制,所以没必要。
      • 注意点:
        • 可能导致精度损失。如:当数值a强转到数值b,若a的数值超过b类型的范围时,会丢失精度。(造成精度降低或溢出)
    • 对于引用数据类型

      • 要求两个对象有多态的关系。否则会出现异常。
  • 注意点:
    - 强转后只能使用强转后的类型拥有的功能。

  • 语法:需要使用强转符:()

double d1 = 12.9;
int i1 = d1;//编译不通过
int i1 = (int) d1;//12,精度损失,截断操作

long l1 = 123;
short s1 = (short) l1;//123,没有精度损失

int i2 = 128;
byte b = (byte) i2;//-128,精度溢出

char c = 'a';
int i3 = (int) c;//97
char c2 = (char) i3;//a

精度溢出

当容量大的数据用容量小的变量接收时,容易造成精度溢出!

拿byte类型来说:为什么 System.out.println((byte)128) 输出为-128?

因为java中的自动转型,因此System.out.println((byte)128) 输出为-128。

在java中默认整型是int类型,int类型是4字节,32位。而byte类型是1字节,8位

而java中的二进制都是采用补码形式存储: ⑴一个数为正,则它的原码、反码、补码相同
⑵一个数为负,则符号位为1,其余各位是对原码取反,然后整个数加1

就int类型的128而言,:

原码为:0000 0000 0000 0000 0000 0000 1000 0000

因为为正数,所以补码与原码相同。

当(byte)128时,将int类型强制转换为byte类型。截断高24位,保留低8位,也就是1000 0000。

于是保存的就是1000 0000。(这里不用进行补码变化,因为直接截断的)

在System.out.println调用时,java类型系统会自动将byte类型转换为int类型,此时进行的是有符号左移操作,前24位全部为1,后8为位1000
0000:1111 1111 1111 1111 1111 1111 1000 0000

然后以计算机中整数以补码形式保存(取反加1):1000 0000 0000 0000 0000 0000 1000 0000

因此System.out.println((byte)128) 输出为-128。即 1000 0000 0000 0000 0000
0000 1000 0000

精度损失

当把整数转换成浮点数时当整数足够大时就有可能导致精度损失 比如吧Integer的最大值转换成float类型
Integer的最大值有32位除去一位符号位还有31位尾数位;
虽然float类型最大也是32位但float除去一位符号位后还有8位指数位只剩下23位尾数位;
所以当Integer的最大值转换成float的时候就会损失精度。

三:运算符

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

注意点: 使用运算符的变量之间要么数据类型一致,要么支持自动类型提升。

分类:

  • 算术运算符
  • 赋值运算符
  • 比较运算符(关系运算符)
  • 逻辑运算符
  • 位运算符
  • 三元运算符

算术运算符

支持基本数据类型。特例:字符串拼接 +

在这里插入图片描述

分析:

//除号 /
int num1 = 12;
int num2 = 5;
int r = num1 / num2;//2,由于是int型,所有小数点后给切掉了。
int r2 = num1 / num2 * num2;//10
double r3 = num1 / num2;//2.0,计算的结果是int型,然后赋值给double型,所以是2.0
double r4 = num1 / (num2 + 0.0);//2.4,自动提升机制,得到想要结果!
double r5 = (double) num1 / num2;//2.4,强转和自动提升,得到想要结果!
double r6 = (double) (num1 / num2);//2.0,注意运算先后顺序。

//***************************************************

//取余运算:%   开发中常用于判断能否被除尽的情况。
//结论:结果的符号与被模数的符号相同。
int m1 = 12;
int n1 = 5;
System.out.println(m1 % n1);//2

int m1 = -12;
int n1 = 5;
System.out.println(m1 % n1);//-2

int m1 = 12;
int n1 = -5;
System.out.println(m1 % n1);//2

int m1 = -12;
int n1 = -5;
System.out.println(m1 % n1);//-2

//***************************************************

//++:自增
/*
** 注意点:
	1.前++和后++的运算顺序有所不同。
	2.自增1不会改变本身变量的数据类型。
	3.不能用于字符串的拼接。
*/
//--:自减,原理同上。

int a = 0;
int b = 0;
int i = 0;
int c = i + a++;//0,a先参与运算,再自增1
int c = i + ++b;//1,b先自增1,再参与运算
++a++;//编译报错
(a++)++;//编译报错

Char c = 'a';
Char b = ++c;//b

short s1 = 10;
s1 = s1 + 1;//编译失败
s1 = (short)(s1 + 1);//正确的
s1 ++;//正确的。自增1不会改变本身变量的数据类型

byte b = 127;
System.out.println(++b);//-128。溢出问题。

String s = "s";
String s1 = ++s;//编译报错

赋值运算符

  • 符号:=

  • 作用:赋值。将右边的变量的内存地址赋给左边的变量。

      int i = 1;
    
  • 特点:

    • 当“ = ” 两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理

    • 如果变量是基本数据类型,此时赋值的是变量所保存的数据值;如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。

      //基本数据类型
      int i = 10;
      int i2 = i;
      System.out.println("i = " + i + " i2 = " + i2);// i = 10 i2 = 10
      i2 = 20;
      System.out.println("i = " + i + " i2 = " + i2);//i = 10 i2 = 20
      //引用数据类型
      class Order {
          int id;
      }
      Order order1 = new Order();
      order1.id = 1001;
      Order order2 = order1;//赋值后二者报存同一地址值,指向堆空间的同一个实体
      System.out.println("order1.id = " + order1.id + " order2.id =" + order2.id);//order1.id = 1001 order2.id = 1001
      order2.id = 1002;
      System.out.println("order1.id = " + order1.id + " order2.id =" + order2.id);//order1.id = 1002 order2.id = 1002
      
    • 支持连续赋值

      int a,b; 
      a = b = 10;
      
  • 扩展赋值运算符:+= -= *= /= %=

    • 特点:运算的结果不会改变变量的数据类型。
    int num1 = 10;
    num1 += 2;//num1 = num1 + 2;
    ...
    num1 %= 2;//num1 = num1 % 2;
    
    String s = "h";
    s += "a";//s = s + "a" 结果是:ha。 注意+=可用在字符串中做拼接运算。
    
    //************************************
    
    short s1 = 10;
    s1 = s1 + 2;//编译失败
    s1 += 2;//正确的
    
    int i = 1;
    i *= 0.1;
    System.out.println(i);//0
    
    //结论:+= 、 -= 、*= 、/= 、%= 运算不会改变变量的数据类型
    

如果想实现变量 +2 的操作,有几种方法?int num = 0;

答:两种。①num = num + 2; ②num += 2;(推荐)

+1 呢?

答:三种。

比较运算符

在这里插入图片描述

分析:

关于==!=

  • 作用:比较两个变量的内容。

    • 对于基本数据类型:比较的是值。即基本数据类型变量存的是值。

      char c = 'a';
      System.out.println(c == 97);//true
      //基本数据类型的变量比较的是数值。
      
    • 对于引用数据类型:比较的是内存地址。即引用数据类型变量存的是堆空间中对象的地址。可以用来比较两个数据是不是同一个对象。

      String s = "hello";//这种声明方法,s的地址也是指向常量池的。
      String s1 ="hello";
      String s2 = new String("hello");//这样,s2地址是指向堆空间的,堆空间的value再指向常量池。
      String s3 = s2;
      System.out.println(s == s1);//true
      System.out.println(s == s2);//false
      System.out.println(s3 == s2);//true,赋值运算符,是把内存地址赋给新的变量。
      
      /*
      结论:== 是比较两个变量的地址。
      */
      
      //练习
      boolean b1 = true;
      boolean b2 = false;
      System.out.println(b2 == b1);//false
      System.out.println(b2 = b1);//true; 把b1赋值给b2,运算后的b2作为形参。
      
  • 不能用于 “基本数据类型与null”、“基本数据类型与引用数据类型” 之间。换句话说,只可以用在 基本数据类型与基本数据类型、引用数据类型与引用数据类型、引用数据类型与null之间。

    String s = "";
    boolean b = (1 == s);//编译报错
    boolean b1 = (1 == null);//编译报错
    boolean b2 = (null == s);//false,运行成功
    

> < >= <=

  • 只能使用在基本数据类型的数据之间。
char c = 'a';
System.out.println(c > 96);//true,自动提升机制

System.out.println(97.6 > 96);//true

String s = "97";
System.out.println(s > 96);//编译报错

逻辑运算符

只适用于boolean类型的变量,结果也是boolean型。
在这里插入图片描述

& 和 && 的区分

  • 同:
    • 运算结果相同
    • 当左边为true时,都会执行右边的判断。
  • 不同:
    • &:两个判断都会执行
    • &&:如果第一个判断为false ,不会继续执行第二个判断。&&也叫短路与运算
boolean b1 = false;
int num1 = 0;
System.out.println(b1 & (num1++ > 0));//false
System.out.println(num1);//1

boolean b2 = false;
int num2 = 0;
System.out.println(b2 && (num2++ > 0));//false
System.out.println(num1);//0;

| 和 || 的区分(与上面类似)

  • | :两个判断都会执行

  • || :如果第一个判断为true ,不会继续执行第二个判断。也叫短路或运算。

开发中,推荐使用 && 和 || ,省资源。

面试题:

class Test {
 public static void main (String[] args) {
     boolean x = true;
     boolean y = false;
     short z = 42;
     //if (y == true)
     if ((z++ == 42) && (y = true)) z++;
     if ((x = false) || (++z = 45)) z++;
     System.out.println("z=" + z);
 }
}

结果为:z=46

位运算符

  • 位运算符执行效率上会比算术运算符高一些。
    在这里插入图片描述

判断 & | ^ 是逻辑运算符还是位运算符的技巧:看操作数据的类型:

  • boolean型:逻辑运算符
  • 整型:位运算符

分析

  • <<、>>、>>>
    在这里插入图片描述
    在这里插入图片描述

  • &、|、^、~

    • ~:各二进制码按补码各位取反。
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

面试题:

  1. 最高效的方式计算 2 * 8 ?为什么?
答:2 << 3 或 8 << 1
  1. >>和>>>的区别?

    • >>:带符号右移。正数右移高位补0,负数右移高位补1。

      4 >> 1,结果是2;
      -4 >> 1,结果是-2。
      -2 >> 1,结果是-1。
      
    • >>>:无符号右移。无论是正数还是负数,高位通通补0。

    • 对于正数而言,>>和>>>没区别。

    • 对于负数而言

      -2 >>> 1,结果是2147483647(Integer.MAX_VALUE),
      -1 >>> 1,结果是2147483647(Integer.MAX_VALUE)。
      

要判断两个数符号是否相同时,可以这么干:
return ((a >> 31) ^ (b >> 31)) == 0;

练习题:

交换两个变量的值。int num1 = 10, num2 = 20;

//方式一:定义临时变量的方式。(推荐使用)
int temp = num1;
num1 = num2;
num2 = temp;

//方式二
//好处:不用定义临时变量
//弊端:①相加操作可能超出存储范围。②有局限性:只适用于数值类型。
num1 = num1 + num2;
num2 = num1 - num2;
num1 = num1 - num2;

//方式三:使用位运算符
//好处:不用定义临时变量
//弊端:有局限性:只适用于数值类型。
num1 = num1 ^ num2;
num2 = num1 ^ num2;
num1 = num1 ^ num2;	

三元运算符

  • 特点:简洁、执行效率高

  • 语法:

    • (条件表达式)?表达式1 : 表达式2;

      条件表达式:
      - 为true:运算后的结果的表达式1
      - 为false:运算后的结果的表达式2
      
    • 要求:

      • 条件表达式的结果须为boolean类型
      • 表达式1 和 表达式2 结果要能够统一类型接收
    • 三元运算符可以嵌套使用

    int m = 12, n = 5;
    int max = (m > n) ? m : n;
    System.out.println(max);//12
    
    double num = (m > n) ? 2 : 1.0;//这样也可以!
    
    //可以嵌套
    String maxStr = (m > n) ? "m大" : ((m == n) ? "m和n相等" : "n大");
    
    //*****************************************
    //获取三个数的最大值
    int n1 = 12;
    int n2 = 30;
    int n3 = -43;
    //int max = (n1 > n2) ? ((n1 > n3) ? n1 : n3) : ((n2 > n3) ? n2 : n3);
    //实际开发中,很少这样写这样的,因为效率并没有变高,还降低了可读性。
    //int max = (((n1 > n2) ? n1 : n2) > n3) ? ((n1 > n2) ? n1 : n2) : n3;
    int max1 = (n1 > n2) ? n1 : n2;
    int max = (max1 > n3) ? max1 : n3;
    System.out.println("三个数中的最大值为:" + max);
    
  • 三元运算符与 if-else 的联系与区别:

    • 三元运算符可简化 if-else 语句
    • 三元运算符要求必须返回一个结果
    • if 后的代码块可有多个语句,三元运算符不行

    如果程序既可以使用三元运算符,又可以使用if-else结构,那么优先选择三元运算符。

    原因:简洁、执行效率高。

运算符的优先级

在这里插入图片描述

使用技巧:需要先运算的,就放在括号 () 里。

单目运算符、三元运算符?目、元指的是参与运算的变量,有几个就是几目、几元。

练习题

1、随意给出一个整数,打印显示它的个位数、十位数、百位数的值。
格式如下:
数字xxx的情况如下:
个位数:
十位数:
百位数:

代码:

class Test {
	
	public static void main(String[] args) {
		method(188);
	}
	
	static void method(int num) {
		int bai = num / 100;
		int shi = num % 100 / 10;//int shi = num / 10 % 10;
		int ge = num % 10;
		
		System.out.println("百位为:" + bai);
		System.out.println("十位为:" + shi);
		System.out.println("个位为:" + ge);
	}
}

结果:
在这里插入图片描述

2、如何求一个0~255范围内的整数的十六进制值,例如60的十六进制表示形式3C?

答:

//方式一:自动实现
String str1 = Integer.toBinaryString(60);
String str2 = Integer.toHexString(60);

//方式二:手动实现
int i1 = 60;
int i2 = i1 & 15;
String j = (i2 > 9) ? (char) (i2 - 10 + 'A') + "" : i2 + "";

int temp = i1 >>> 4;
i2 = temp & 15;
String k = (i2 > 9) ? (char) (i2 - 10 + 'A') + "" : i2 + "";

System.out.println(k + "" + j);

解析:
0 ~ 255 范围内:所以二进制 8 位。 xxxx xxxx,
转16进制,即每4位二进制得一位十六进制 xxxx => x
所以,思路是分别求出 两个 xxxx,使用字符串拼接。
    60:二进制:0011 1100
    1.150000 1111&(与运算):0000 1100  => 得到后42.使用三元运算,将其变成十六进制表现形式,字符串接收。
    3.将原来的数无符号右移4位,得到前4位:0000 0011
    4.同样的方法转为十六进制表现形式
    5.拼接两个字符串,得到完整的结果。

四:程序流程控制

流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。
其流程控制方式采用结构化程序设计中规定的三种基本流程结构,特点如下:(以Java语言描述)

  • 顺序结构
    • 程序从上到下逐行地执行,中间没有任何判断和跳转
  • 分支结构(也叫条件判断语句):有 if…else 和 switch-case 两种分支语句(算上三元运算符就是三种)
    • 根据条件,选择性地执行某段代码
    • 可以嵌套使用
  • 循环结构:有while、do…while 、for 三种循环语句结构。(开发中使用for、while多一些)
    • 根据循环条件,重复性地执行某段代码
    • 循环可以嵌套
    • JDK 1.5 提供了foreach 循环,更方便地遍历集合、数组元素
    • 循环结束情况:①循环条件返回false;②遇到关键字 break、return
    • 要小心无限循环:
      在这里插入图片描述

分支结构

if-else结构
在这里插入图片描述
在这里插入图片描述

使用说明:

  • 条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量

  • 语句块只有一条执行语句时,一对{}可以省略,但建议保留

    if(true) System.out.println("执行了");
    
  • 可以嵌套使用

  • else是可选的,可以根据需要省略

  • 想得出正确结果,要注意逻辑:

    • 当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓
    • 当多个条件是“包含”关系时,要考虑哪个结构写在上面:“小上大下 / 子上父下”
    int i = 89;
    if (i >= 60) System.out.println("奖励一个ipad");
    else if (i >= 80) System.out.println("奖励一个iphoneX");
    else System.out.println("什么也没有");
    
    //这时候编译运行没有错误,但得不到想要的结果,是因为逻辑上的错误!
    

示例:多选一操作:

int i = 0;
//多选一判断
//方式一
if (i++ < 0) System.out.println("一");
if (i++ > 0) System.out.println("二");
if (i++ == 0) System.out.println("三");
System.out.println(i);//3
//方式二
if (i++ < 0) System.out.println("一");
else if (i++ > 0) System.out.println("二");
else if (i++ == 0) System.out.println("三");
System.out.println(i);//2
/*
结论:方式二更优,方式一会把所有判断都执行,方式二判断成立后不会继续往下判断。
*/

练习题:

1、对下列代码,若有输出,指出输出结果。

int x = 4;

int y = 1;

if (x > 2) {

if (y > 2)

System.out.println(x + y);

System.out.println(“atguigu”);

} else

System.out.println("x is " + x);

答:有,atguigu

题1变相:

int x = 4;

int y = 1;

if (x > 2)

​ if (y > 2) //这个串联起下面的,共同构成一个执行语句。

​ System.out.println(x + y);

else //就近原则,与最近的 if 配对了。

​ System.out.println("x is " + x);

答:有,x is 4

2、

boolean b = true;

//如果写成if(b=false)能编译通过吗?如果能,结果是? 答:能,结果 c

if(b == false)

System.out.println(“a”);

else if(b)

System.out.println(“b”);

else if(!b)

System.out.println(“c”);

else

System.out.println(“d”);

答:输出 b

switch-case
在这里插入图片描述

使用说明:

  1. switch 结构中的表达式,只能是如下的 6 种数据类型之一:byte、short、char、int、String(JDK 7.0 新增)、枚举类型(enmu ->JDK 5.0新增) (言外之意:JDK 5.0 前 这个表达式只能是4种数据类型之一)

  2. case 之后只能声明常量,不能声明范围。一旦表达式匹配case 常量,就会执行后面全部代码(再下面的case 语句不需要判断,继续执行。所以一般会在case 语句后加 break;跳出switch-case,避免执行不需要的语句)

    • case语句中的变量的作用范围是整个switch结构。

      switch (num) {
          case 1:
              int i = 0;
              System.out.println(1);
              break;
          case 2:
              int i = 0;//编译错误,i的作用范围在整个switch结构里。
              System.out.println(2);
              break;//最后的这个break可省
      }
      
  3. break:可选的,执行break语句跳出switch-case,不加的话,就会把所有的case语句都执行完

  4. default :表示其他情况,相当于if-else结构里的else,位置是灵活的,放哪都行

    int num = 2;
    switch (num) {
        case 1:
            System.out.println(1);
            break;
        default:
            System.out.println(0);
            break;
        case 2:
            System.out.println(2);
            break;//最后的这个break可省
    }
    
    //运行结果:2
    //如果 num 改为 3,则结果是:0
    
    //与下面的无异
    switch (num) {
        case 1:
            System.out.println(1);
            break;
        case 2:
            System.out.println(2);
            break;
        default:
            System.out.println(0);
            break;
    }
    
  5. 与if-else联系:

    1. 凡是可以使用switch-case的结构,都可以转换为 if-else。反之,不成立。
    2. 我们写分支结构时,当发现既可以使用 switch-case,(同时,switch中表达式的取值情况不太多),又可以使用 if-else 时,我们优先选择使用 switch-case。原因:switch-case执行效率稍高。

练习题:

根据用于指定月份,打印该月份所属的季节。

3,4,5 春季 6,7,8 夏季 9,10,11 秋季 12, 1, 2 冬季

答:

...
 
Scanner scanner = new Scanner(System.in);
int month = scanner.nextInt();
switch (month) {
 //技巧:如果switch-case结构中的多个case的执行语句相同,则可以考虑进行合并。
 case 3:
 case 4:
 case 5:
     System.out.println("春季");
     break;
 case 6:
 case 7:
 case 8:
     System.out.println("夏季");
     break;
 case 9:
 case 10:
 case 11:
     System.out.println("秋季");
     break;
 case 12:
 case 1:
 case 2:
     System.out.println("冬季");
     break;
 default:
     System.out.prinltn("输入错误!")
}

编写程序:从键盘上输入“year"的“month”和“day”,要求通过程序

输出输入的日期为第几年的第几天。

答:

...

Scanner scanner = new Scanner(System.in);
System.out.print("请输入年份:");
int year = scanner.nextInt();
System.out.print("请输入月份:");
int month = scanner.nextInt();
System.out.print("请输入该月的号数:");
int day = scanner.nextInt();
int sumDays = 0;
boolean isRun = false;

//判断输入是否合法
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
isRun = true;
}
if (month < 0 && month > 12 || day < 0 || year < 0) {
System.out.println("输入有误!");
return;
} else if (month == 2) {
 if (isRun) {
     if (day > 29) {
      System.out.println("输入有误!");
      return;
  	}
 } else {
     if (day > 28) {
      System.out.println("输入有误!");
      return;
  }
 }
  
} else if (((month - 8) % 2 == 0 && month >= 8) || ((7 - month) % 2 == 0 && month <= 7)) {
if (day > 31) {
  System.out.println("输入有误!");
  return;
}
} else {
if (day > 30) {
  System.out.println("输入有误!");
  return;
}
}
/*
switch (month) {
case 1:
  sumDays = 31;
  break;
case 2:
  sumDays = 31 + 28;
  break;
case 3:
  sumDays = 31 + 28 + 31;
  break;
...
//这样写的代码:冗余
}
*/
//改进
switch (month) {
case 12:
  sumDays += 30;
case 11:
  sumDays += 31;
case 10:
  sumDays += 30;
case 9:
  sumDays += 31;
case 8:
  sumDays += 31;
case 7:
  sumDays += 30;
case 6:
  sumDays += 31;  
case 5:
  sumDays += 30;
case 4:
  sumDays += 31;
case 3:
  if (isRun) sumDays += 29;
  else sumDays += 28;
case 2:
  sumDays += 31;
case 1:
  sumDays += day;
}
System.out.println("输入的日期为 " + year + " 年的第 " + sumDays + " 天!");

循环结构

for循环
在这里插入图片描述

for (int i = 1; i <= 5; i++) { //i:在for循环里有效,出了循环就失效了。
    System.out.println("hello world!");
}

//练习
int num = 1;
for (System.out.print('a'); num <= 3; System.out.print('c'), num++) {
    System.out.print('b');
}
//输出结果:abcbcbc

foreach循环

  • jdk5新增

  • 语法:
    在这里插入图片描述

  • 使用:

  1. 可以用来遍历数组、集合。
  • 示例:

    int[] arr = {1, 2, 3, 4, 5};
    for (int i : arr) {
        System.out.println(i);
    }
    

while循环

循环的条件:

①:初始化条件
②:循环条件:boolean型
③:迭代条件
④:循环体

格式:

while(②) {

​ ④;

​ ③;

}

执行过程:① -> ② -> ④ -> ③

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

特点:

  • 和for循环执行差别不大,就是while的初始化条件在循环外(初始化条件的作用范围不同)。
  • 与do-while循环区别:whie循环结构是先判断再执行循环体

do-while 循环

循环的条件:

①:初始化条件
②:循环条件:boolean型
③:迭代条件
④:循环体

格式:

do {

​ ④;

​ ③;

} while (②)

执行过程:① -> ④ -> ③ -> ②

特点:

  • 至少会执行一次循环体
  • 与while循环区别:do-whie循环结构是先执行循环体再判断。
  • 和for循环区别:do-while循环初始化条件在循环外。
int num = 2;
do {
    System.out.println(num);
    num--;
} while (num > 1);
//结果:2,1

while (num > 1) {
	System.out.println(num);
    num--;
}
//结果:2,1

//但如果,num = 1,do-while会输出1,而while不会输出。
//结论:正常执行循环下两个循环结构结果无异,但当一开始就不满足进入循环条件时,do-while会执行一次循环而while不会。

相关关键字的使用

  • break:

    • 作用:结束当前循环,匹配最近的循环
    • 使用范围:switch-case、循环结构中
  • continue:

    • 作用:结束当次循环,匹配最近的循环
    • 使用范围:循环结构中
  • 相同点:关键字后面的语句不合法:后面不能加执行语句。

带标签的break、continue的使用

Java的控制循环结构中是没有关键字goto的,这种做法有它的好处,它提高了程序流程控制的可读性,但是也有不好的地方,它降低了程序流程控制的灵活性。所以,Java为了弥补这方面的不足,Java提供了break和continue的标签用法。

  • Java中的标签就是一个紧跟着“:”的标识符。
  • 与其他语言不同,Java语言的标签必须放在循环前面才有作用。
  • 标签名不是固定的,可以自定义。
label:for(int i = 1; i < 4; i++) {
    for(int j = 1; j < 10; j++) {
        if(j % 4 == 0) {
            break label; //结束指定标识的循环结构。向外匹配最近的标签。
            //continue label;  //结束指定标识的循环结构当次循环
        }
    }
}

练习题

分支

1、我家的狗5岁了,5岁的狗相当于人类多大呢?其实,狗的前两年每

一年相当于人类的10.5岁,之后每增加一年就增加四岁。那么5岁的狗

相当于人类多少年龄呢?应该是:10.5 + 10.5 + 4 + 4 + 4 = 33岁。

编写一个程序,获取用户输入的狗的年龄,通过程序显示其相当于人

类的年龄。如果用户输入负数,请显示一个提示信息。

答:

import java.util.Scanner;
class Test {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		System.out.print("请输入狗的年龄:");
		int dogAge = scanner.nextInt();
			
		if (dogAge >= 0 && dogAge <= 2) {
			System.out.println("相当于人的年龄:" + dogAge * 10.5);
		} else if (dogAge > 2) {
			System.out.println("相当于人的年龄:" + (2 * 10.5 + (dogAge - 2) * 4));
		} else {
			System.out.println("狗狗还没出生呢!");
		}
	}
}

循环

1、遍历100以内的偶数,记录所有偶数的和

int sum = 0;//变量使用前要先赋值
//int count;//可以设置计算器
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
  System.out.println(i);
  sum += i;
  //count++;
}
}
System.out.println(sum);

2、输入两个正整数m和n,求其最大公约数和最小公倍数。

比如:12和20的最大公约数是4,最小公倍数是60。

...
Scanner scanner = new Scanner(System.in);
int m = scanner.nextInt();
int n = scanner.nextInt();

//获取最大公约数
//1.获取两个数中的较小值
int min = (m <= n) ? m : n;
//2.遍历
for (int i = min; i >= 1; i--) {
if (m % i == 0 && n % i == 0) {
  System.out.println("最大公约数:" + i);
  break;//跳出循环
}
}
//获取最小公倍数
int max = (m >= n) ? m : n;
for (int i = max; i <= m * n; i++) {
if (i % m == 0 && i % n == 0) {
  System.out.println("最小公倍数:" + i);
  break;
}
}

3、输出所有的水仙花数,所谓水仙花数是指一个3位数,其各个位上数字立方和等于其本身。
**例如:153 = 1*1*1 + 3*3*3 + 5*5*5 **

class ShuiXianHua {
	public static void main(String[] args) {
		for (int i = 100; i < 1000; i++) {// 实现所有的三位数的一个遍历
			int j1 = 0;
			int j2 = 0;
			int j3 = 0;
			j1 = i / 100;// 百位
			j2 = (i - 100 * j1) / 10;// 十位
			j3 = i - 100 * j1 - 10 * j2;// 个位

			if (i == j1 * j1 * j1 + j2 * j2 * j2 + j3 * j3 * j3) {
				System.out.println("此数值为满足条件的水仙花数:" + i);
			}
		}
	}
}

4、100以内的所有质数的输出。

质数:素数,只能被1和它本身整除的自然数。

boolean isFlag = true;//标识是否被除尽
for (int i = 1; i <= 100; i++) {
 for (int j = 2; j <= i; j++) {
     if (i % j == 0) {
         isFlag = false;
     }
 }
 if (isFlag) {
     System.out.println(i);
 }
 isFlag = true;//需重置
}

//优化

boolean isFlag = true;
for (int i = 1; i <= 100; i++) {
 for (int j = 2; j <= Math.sqrt(i); j++) {//优化二:Math.sqrt(i):根号i:对本身是质数的自然数是有效的。
     if (i % j == 0) {
         isFlag = false;
         break;//优化一:对非质数有效。可以通过System.currentTime()计算运行时间
     }
 }
 if (isFlag) {
     System.out.println(i);
 }
 isFlag = true;
}

//方式二
outer:
for (int i = 1; i <= 100; i++) {
 for (int j = 2; j <= Math.sqrt(i); j++) {
     if (i % j == 0) {
         continue outer;
     }
 }
 System.out.println(i);
}

5、一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如:6=1+2+3。

编程,找出1000以内的所有完数。(因子:除去这个数本身的其他的数)

答:

int factor = 0;
for (int i = 1; i <= 1000; i++) {
 for (int j = 1; j <= i/2; j++) {//优化:j <= i/2
     if (i % j == 0) {
         factor += j;
     }
 }
 if (i == factor) {
     System.out.println(i);
 }
 factor = 0;//需重置
}

五:数组

数组的概述

什么是数组?

  • 数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。

数组常见概念:数组名,下标(或索引),元素,数组的长度(元素的个数)。

数组的特点:

  • 创建数组对象会在内存中开辟一整块连续的空间(在堆空间中),而数组名中引用的是这块连续空间的首地址。
  • 有序排列的(下标是有序的),
    • 使用下标访问指定位置上的元素。(数组的角标(或索引)是从0开始的,到 数组的长度-1 结束)。
  • 数组的长度一旦确定,就不能修改。
  • 数组本身是引用数据类型,而数组中的元素可以是任何数据类型。
  • 在数据结构中属于顺序表结构。

数组的分类

  • 按照维度:一维数组、二维数组、三维数组 …
  • 按元素的数据类型分:元素为基本数据类型的数组、元素为引用数据类型的数组(即对象数组)

数组是一个类吗?

  • 数组是有对应的类,这个类是在JVM运行时创建的,所以没有对应的class 文件。

  • 数组的类名是:[开头的,和普通类的不一样。

  • 数组类中不包含任何成员和变量(可以通过getClass拿到 Class 对象来查看),数组的长度length是通过JVM的指令 arraylength 直接得到的。

  • 数组的类和一般类在JVM中是区分对待的,JVM会对数组类做一些特殊的操作,比如数组类的对象创建是通过JVM指令直接执行的,比如 newarray 创建一个数组对象,multianewarray创建多个数组对象。

  • 数组类并不是只有一个类,而是会有很多个。数组类的类型是由数组的内容和维度同时决定的。比如:int[] 的类名是:[I ;int[][] 的类名是:[[I(其中的I 是 int 类型的在虚拟机指令中数据类型)。这是两个不同的类。

数组的使用

一维数组的使用

  1. 创建数组:创建数组对象,赋给一个数组变量。

    //数组类型变量的声明:数据类型[] 
    int[] nums;//常用 
    int nums[];
    //效果一样:变量名都是nums
    
    //初始化:也叫创建数组对象 new
    //静态初始化:数组的初始化和数组元素的赋值操作同时进行
    nums = new int[]{1001,1002,1003,1004}; //自定义初始化数据,[]里不能有值,长度根据{}里元素的个数。
    int[] arr = {1,2,3}; //类型推断
    int[] a;
    a = {1,3,2};//编译不通过。类型推断不能这样用,类型推断必须和声明一起用。
    
    //动态初始化:数组的初始化和数组元素的赋值操作分开进行
    String[] names = new String[5];//有默认的初始化值。[]里的数一定要给的,表示长度,不然编译不通过。
    
    /*
    总结:数组一旦初始化完成后,其长度就确定了。 
    */
    
  • 数组元素的默认初始化值:这是Java对于各个数据类型默认值的规范。Java语言中其他地方也这么用。

       整型数组:0
       浮点型:0.0
       字符型:0'\u0000' ,而非'0'
       布尔型:false
       引用数据类型:null
    
  1. 调用数组中的元素:根据索引取值,也可以根据索引赋值。

    String[] names = new String[5];
    
    //通过角标的方式调用
    //数组的角标(或索引)是从0开始的,到 数组的长度-1 结束
    String name = name[0];//取值
    names[0] = “你好”;//赋值
    
    names[6] = "n";//超出数组的长度,编译通过,但运行会报数组角标越界异常。
    
  2. 获取数组的长度:数组的属性:length

    int[] names = new int[]{1001,1002,1003,1004};
    //属性:length
    int l = names.length;
    
  3. 遍历数组:

    int[] names = new int[]{1001,1002,1003,1004};
    
    for(int i = 0; i < names.length; i++) {
        System.out.println(names[i]);
    }
    
  4. 数组的内存解析
    在这里插入图片描述
    在这里插入图片描述

    数组中的数据最终都是(指向)方法区中的。

多维数组的使用

说明:

  • Java 语言里提供了支持多维数组的语法
  • 从数组底层的运行机制来看,其实没有多维数组
    • 以二维数组为例:对于二维数组的理解,可以看成是一维数组 array1 又作为另一个一维数组 array2 的元素而存在。多维数组就是以这种方式衍生出来。其实,从数组底层的运行机制来看,其实没有多维数组。

使用(以二维数组为例):

  1. 声明和初始化的方式。(原理和一维数组无异,是由一维数组衍生出来的)

    //声明
    int[][] a1;
    int a2[][];
    int[] a3[];
    
    //初始化
    //静态初始化
    int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
    int[][] arr5 = {{1,2,3},{4,5},{6,7,8}};//类型推断,注意类型推断须和声明一起使用。
    //动态初始化1
    String[][] arr2 = new String[3][2];//使用此方式时各个维度的长度都已确定。
    //动态初始化2
    String[][] arr3 = new String[3][];//这里第二个[]不用指定长度是因为其作为数组中的元素有默认值null
    //String[][] arr3 = new String[][];//是错的,编译不通过
    
    • 数组的默认初始化值

      //规定,二维数组分外层元素和内层元素
      //外层元素:arr[0],arr[1]等
      //内层元素:arr[0][0],arr[1][2]等
      
      针对初始化方式一:如:int[][] arr = new int[4][3];
              》外层元素:地址值
              》内层元素:和一维数组一样
      针对初始化方式二:如:int[][] arr = new int[4][];
              》外层元素:null
              》内层元素:不能调用,否则空指针异常
                        
      
      int[][] arr = new int[4][3];
      System.out.println(arr[0]);//[I@15db9736 (地址值:指向堆空间的数组对象)
      System.out.println(arr[0][0]);//0 (数组的默认初始化值)
      
      System.out.println(arr);//[[I@6d65d65c (地址值)
      
  2. 调用数组的指定位置的元素,根据索引取值,也可以根据索引赋值。

    int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
    String[][] arr2 = new String[3][2];
    String[][] arr3 = new String[3][];
    
    //取值
    arr1[0][1]; //2
    arr2[1][1]; //null,前面讲过的默认初始化值
    arr3[1][0]; //空指针,arr3[1]是null,null的[0]:null没有索引,所以空指针。
    //将其声明为一个数组类型,就不会报空指针:
    //arr3[1] = new String[4];//赋值
    //arr3[1][0]; //null
    
  3. 获取数组长度

    int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
    
    //使用数组的属性:length
    arr1.length;  //3
    arr1[1].length; //2
    
  4. 遍历

    for(int i = 0; i < arr4.length; i++) {
        for(int j = 0; j < arr4[i].length; j++) {
            System.out.pritnln(arr4[i][j]);
        }
    }
    
  5. 数组的内存解析
    在这里插入图片描述

例题

1、从键盘读入学生成绩,找出最高分,并输出学生成绩等级。

成绩>=最高分-10 等级为’A’

成绩>=最高分-20 等级为’B’

成绩>=最高分-30 等级为’C’

其余 等级为’D’

提示:先读入学生人数,根据人数创建int数组,存放学生成绩

import java.util.Scanner;

public class Test {

 public static void main(String[] args) throws Exception {
     //1.使用Scanner,读取学生个数
     Scanner scanner = new Scanner(System.in);
     System.out.print("请输入学生人数:");
     int number = scanner.nextInt();
     //2.创建数组,存储学生成绩:动态初始化
     int[] scores = new int[number];
     //3.给数组中的元素赋值
     //4.获取数组中的元素的最大值:最高分
     int maxScore = 0;
     System.out.println("请输入" + number + "个学生成绩:");
     for (int i = 0; i < scores.length; i++) {
         scores[i] = scanner.nextInt();
         if (maxScore < scores[i]) maxScore = scores[i];
     }
     System.out.println("学生成绩的最高分是:" + maxScore);
     //5.根据每个学生成绩与最高分的差值,得到每个学生的等级,并输出等级和成绩
     char level;
     for (int i = 0; i < scores.length; i++) {
         if (maxScore - scores[i] <= 10) {
             level = 'A';
         } else if (maxScore - scores[i] <= 20) {
             level = 'B';
         } else if (maxScore - scores[i] <= 30) {
             level = 'C';
         } else {
             level = 'D';
         }
         System.out.println("student" + i + "'s score is" + scores[i] + "and grade is " + level);
     }
     //关闭资源
     scanner.close();
 }
}
//运行结果
请输入学生人数:5
请输入5个学生成绩:
62
30
92
85
78
学生成绩的最高分是:92
student0's score is62and grade is C
student1's score is30and grade is D
student2's score is92and grade is A
student3's score is85and grade is A
student4's score is78and grade is B

2、声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是:

**a ) x[0] = y; ** no

**b) y[0] = x; **yes

**c) y[0][0] = x; **no。理解:y[0][0]是int型的,x是int[]型的

**d) x[0][0] = y; **no

**e) y[0][0] = x[0]; **yes

**f) x = y; **no

提示:

一维数组:int[] x 或者int x[]

二维数组:int[][] y 或者 int[] y[] 或者 int y[][]

数组涉及的常见算法

  1. 数组元素的赋值(杨辉三角、回形数等)

  2. 求数值型数组中元素的最大值、最小值、平均数、总和等

  3. 数组的复制、反转、查找(线性查找、二分法查找)

    • 复制

      int[] array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19};
       //实现复制
       int[] array3 = new int[array1.length];
       for (int i = 0; i < array3.length; i++) {
           array3[i] = array1[i];
       }
      
    • 反转

      String[] arr = new String[]{"jj", "aa", "bb", "cc"};
      //数组的反转
      for (int i = 0; i < arr.length / 2; i++) {
          String temp = arr[i];
          arr[i] = arr[arr.length - i - 1];
          arr[arr.length - i - 1] = temp;
      }
      
    • 查找

      • 线性查找:就简单的,同时效率也是最差的。

        String[] arr = new String[]{"jj", "aa", "bb", "cc"};
        //线性查找
        String test = "bb";
        for (int i = 0; i < arr.length / 2; i++) {
            if (test.equals(arr[i])) {
                System.out.println("找到了");
                break;
            }
        }
        
      • 常用的有:二分法查找等

  4. 数组元素的排序算法:冒泡排序、快速排序等

例题

1、使用二维数组打印一个 10 行杨辉三角。

【提示】

1. 第一行有 1 个元素, 第 n 行有 n 个元素

2. 每一行的第一个元素和最后一个元素都是 1

3. 从第三行开始, 对于非第一个元素和最后一个元

素的元素。即:

yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IApjrAf4-1625911129420)(C:\Users\17812\Desktop\笔记(work)]\typora_images\image-20210331223910731.png)

import java.util.Scanner;

public class Test {

 public static void main(String[] args) throws Exception {
     //1.声明并初始化二维数组
     int[][] yangHui = new int[10][];
     //2.给数组的元素赋值,并输出
     for (int i = 0; i < yangHui.length; i++) {
         yangHui[i] = new int[i + 1];
         //给首末元素赋值
         yangHui[i][0] = yangHui[i][i] = 1;
         //给每行的非首末元素赋值  
         for (int j = 1; j < yangHui[i].length - 1; j++) {
             yangHui[i][j] = yangHui[i - 1][j - 1] + yangHui[i - 1][j];
         }
         //输出
         for (int a = 0; a < yangHui[i].length; a++) {
             System.out.print(yangHui[i][a] + "\t");
         }
         System.out.println();        
     }
 }
}

2、定义一个int型的一维数组,包含10个元素,分别赋一些随机整数,

然后求出所有元素的最大值,最小值,和值,平均值,并输出出来。

要求:所有随机数都是两位数。

提示;

[0,1) * 90 -> [0,90) -> 10 -> [10,100) -> [10,99]

(int)(Math.random() * 90 + 10)

import java.math.*;

public class Test {

 public static void main(String[] args) throws Exception {
     int[] arr = new int[10];
     //最大、最小值
     int maxValue = 0;
     int minValue = 0; 
     //总和
     int sum = 0;   
     //平均数
     double avgValue; 

     for (int i = 0; i < arr.length; i++) {
         arr[i] = (int) (Math.random() * 90 + 10);
         //求数组元素的最大值、最小值
         if (i == 0) {
             maxValue = arr[0];//更完美,int maxValue = 0; => 不适合有全为负数的情况
     		minValue = arr[0];
         } else {
             if (maxValue < arr[i]) maxValue = arr[i];
             if (minValue > arr[i]) minValue = arr[i];
         }
         sum += arr[i];//求总和
         System.out.print(arr[i] + "\t");
     }
     System.out.println();
     avgValue = (double) sum / arr.length;//求平均数
     System.out.println("最大值为:" + maxValue);
     System.out.println("最小值为:" + minValue);
     System.out.println("总和为:" + sum);
     System.out.println("平均数为:" + avgValue);
 }
}

3、使用简单数组

(1)创建一个名为ArrayTest的类,在main()方法中声明array1和array2两个变量,

他们是int[]类型的数组。

(2)使用大括号{},把array1初始化为8个素数:2,3,5,7,11,13,17,19。

(3)显示array1的内容。

(4)赋值array2变量等于array1,修改array2中的偶索引元素,使其等于索引值

(如array[0]=0,array[2]=2)。打印出array1。

思考:array1和array2是什么关系?

拓展:修改题目,实现array2对array1数组的复制

import java.math.*;

public class Test {

 public static void main(String[] args) throws Exception {
     int[] array1, array2, array3;
     array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19};

     for (int i = 0; i < array1.length; i++) {
         System.out.print(array1[i] + "\t");
     }
     System.out.println();
     array2 = array1;
     for (int i = 0; i < array2.length; i++) {
         if (i % 2 == 0) array2[i] = i;
     }
     for (int i = 0; i < array1.length; i++) {
         System.out.print(array1[i] + "\t");
     }
     System.out.println();
     /**
         * 结果:
         * 2       3       5       7       11      13      17      19
         * 0       3       2       7       4       13      6       19
         * 
         * 思考:
         * 》array1和array2的地址相同,指向堆空间中同一个数组实体。这不能称作数组的复制。
        */

        
        array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19};
        for (int i = 0; i < array1.length; i++) {
            System.out.print(array1[i] + "\t");
            array2[i] = array1[i];
        }
        System.out.println();
        
        //实现复制
        array3 = new int[array1.length];
        for (int i = 0; i < array3.length; i++) {
            array3[i] = array1[i];
        }
        
        for (int i = 0; i < array3.length; i++) {
            if (i % 2 == 0) array3[i] = i;
        }
        for (int i = 0; i < array1.length; i++) {
            System.out.print(array1[i] + "\t");
        }
        System.out.println();
        /**
         * 结果:
         * 2       3       5       7       11      13      17      19
           2       3       5       7       11      13      17      19

           思考:
           》此时,修改array3并不会改变array1的值,两者互不影响,在内存中各有独立的空间。实现了数组的复制。
         */
    }
}

Arrays 工具类

介绍:java.util.Arrays类即为操作数组的工具类

包含了用来操作数组(比如排序和搜索)的各种方法。 如:

  • 有许多重载方法用于不同数据类型的使用,结构都一样,下面只展示一种。

    • 下面展示的源码都是jdk1.8的

    • equals(…):

      //源码
      	public static boolean equals(int[] a, int[] a2) {
              if (a==a2)
                  return true;
              if (a==null || a2==null)
                  return false;
      
              int length = a.length;
              if (a2.length != length)
                  return false;
      
              for (int i=0; i<length; i++)
                  if (a[i] != a2[i])
                      return false;
      
              return true;
          }
      
    • toString(…):

      //源码
      	public static String toString(int[] a) {
              if (a == null)
                  return "null";
              int iMax = a.length - 1;
              if (iMax == -1)
                  return "[]";
      
              StringBuilder b = new StringBuilder();
              b.append('[');
              for (int i = 0; ; i++) {
                  b.append(a[i]);
                  if (i == iMax)
                      return b.append(']').toString();
                  b.append(", ");
              }
          }
      
    • fill(…):

      //源码
          public static void fill(long[] a, long val) {
              for (int i = 0, len = a.length; i < len; i++)
                  a[i] = val;
          }
      
    • sort(…):

      //源码
      	public static void sort(int[] a) {
              DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
              /**
              DualPivotQuicksort:双基点快排。Java将其封装成一个类供使用。
              结论:
              》底层用的是快排。
              */
          }
      
      
    • binarySearch(…)

      //源码
          public static int binarySearch(int[] a, int key) {
              return binarySearch0(a, 0, a.length, key);
          }
          private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                           int key) {
              int low = fromIndex;
              int high = toIndex - 1;
      
              while (low <= high) {
                  int mid = (low + high) >>> 1;
                  int midVal = a[mid];
      
                  if (midVal < key)
                      low = mid + 1;
                  else if (midVal > key)
                      high = mid - 1;
                  else
                      return mid; // key found
              }
              return -(low + 1);  // key not found.
          }
      
;