Java三部曲之JavaSE基础

目录

前言

编程要领

计算机组成

第一章:Java语言概述

1.1基础常识

1.1.1软件开发

1.1.2人机交互

1.1.3常用的DOS命令(磁盘操作系统)

1.2Java语言介绍

1.2.1程序语言历史

1.2.2Java语言的主要特性

1.3Java安装

1.4第一段程序

第二章:Java基本语法

2.1关键字

2.1.1关键字介绍

2.2标识符

2.2.1定义规则:

2.2.2编程风格:

2.3变量

变量的概念:

2.3.1按数据类型区分

2.3.2按变量的声明位置来分

2.3.3进制

2.4运算符

2.4.1 位运算符

2.4.2 算术运算符

2.4.3赋值运算符

2.4.5逻辑运算符

2.4.6三元运算符

2.4.7命令行参数

2.4.8运算符优先级

2.5程序流程控制

2.5.1顺序结构

2.5.2分支结构

2.5.3循环结构

2.5.4特殊流程控制语句

2.6方法

2.6.1方法介绍

2.6.2方法的重载

2.6.3方法的参数传递分析

2.7项目1《家庭记账软件》

第三章:面向对象编程

3.1面向对象与面向过程

3.2数组

3.3项目2:客户信息管理软件

第四章:高级类特性

4.1继承

4.2方法覆盖

4.3构造器在子类中的调用

4.4多态

4.5Object类

4.6关键字static

4.7单例设计模式

4.8链表

4.9初始化块

4.10关键字:final

4.11抽象类

4.12接口

4.13内部类

 第五章:枚举与注解

5.1枚举

5.2注解

第六章:异常处理

第七章:Java常用类

7.1包装类

7.2String类

7.3StringBuffer类与StringBuilder类


前言

编程要领

多写代码,要加注释。

计算机组成

硬件:看得见摸得着的电子器件

{

cpu中央处理器:执行指令,指令是比命令单位更小的命令,如add,数据5,8可通过此指令得到5+8的结果。实际上指令也是数据。

内存:保存的是程序(指令和数据的集合),与cpu进行数据交换,帮助cpu保存数据,事实上内存的速度比cpu慢很多,最好让它们的频率一致。

外存:指硬盘、光驱等,作用也是保存程序和普通文件,因为内存断电就会丢失数据,所以用外存可以持久保存数据,但是外存与cpu进行数据交换速度很慢,所以要用到内存。

事实上,程序一旦进入内存,就会变成运行中,我们称其为进程(进行中的程序)。

}

软件:

看不见摸不着的计算机指令和数据的集合

第一章:Java语言概述

1.1基础常识

1.1.1软件开发

一系列计算机数据与指令的集合,包括系统软件(人通过系统软件与硬件交互)与应用软件。

1.1.2人机交互

图形化界面,简单直观。

命令行方式,复杂高效:

a)控制台 启动控制台 win+r->cmd(command),回车(是一个动令,与前面的静态命令区分开),控制台发出的命令交给操作系统,在传给硬件去执行。

1.1.3常用的DOS命令(磁盘操作系统)

dir:列出当前目录的子文件与子目录,输入 dir,目录就是一个特殊的数据结构,是一个可以无限延伸的容器,输入dir:

Java三部曲之JavaSE基础_第1张图片

是目录标记,没有目录标记的是文件,43代表的是文件大小为43个字节,一个字节=8bit,bit只能存储0or1。

当前目录是通过路径来体现的,如C:\Users\ZXF>_ ,其中>是前导符,_是光标,C:\Users\ZXF是当前目录,本质是一个path,路径是一条路,尽头指向一个文件或目录,\是父目录与子目录的分隔符

切换盘符:输入盘符:回车

md:创建目录,输入md 目录名列表(命令行参数)

rd:删除目录,但只能删除空目录

rd/s 回车 ->y:删除非空文件夹

rd/s/q:直接删除文件夹,慎用

cd:进入指定目录,输入 cd 路径,要进入D:\Mywork\JavaSE\day01,其路径分为相对路径与绝对路径,

相对路径是以当前目录为出发点,如在D:/Mywork目录下,相对路径即cd JavaSE/day01,绝对路径是以根目录为出发点,即cd D:/Mywork/JavaSE/day01,只要知道绝对路径,就总能直接找到该目录,(我想知道 \ 与 / 有区别吗)

根目录不同时,无法直接切换,需要加上/d,如cd /d D:/Mywork/JavaSE/day01。

cd..:退回上一级目录,输入cd..是回到上一级目录,cd../../.. 可以回到上三级的目录

cd\:退回根目录(root)

del:删除文件,输入del 目录名列表

exit:退出dos命令行

ds:清屏

notepad:打开记事本

1.2Java语言介绍

1.2.1程序语言历史

{

打孔机:纯机器语言,面向硬件开发,01语言。

汇编语言:面向CPU开发,受CPU型号限制,主要是操作系统开发。

C、Pascal、Fortran语言:面向过程开发,C++语言是面向过程/面向对象开发,它们是面向操作系统

OS,受OS限制。

Java语言:面向谁,就受谁限制,不能跨CPU与OS等平台,而Java是一门跨平台的纯面向对象开发的

语言,它面向虚拟机(JVM),而JVM有多个OS适配的版本。

}

1.2.2Java语言的主要特性

简单:语法与C\C++类似,但相对来说简单一些,容易过渡。

面向对象:可以提供类、接口、继承等,为了简单起见,只支持类之间的单继承,但支持接口之间的多

继承,并支持类与接口之间的实现机制(关键字为implements)。相对于面向过程,面向过程要直接设计全流程,但是需要一个问题一个方案,复用性较差,而面向对象是模块化,将解决本问题需要的对象都找出来,对象中再去面向过程设计每个小模块的流程,并且这些对象可以重复用在类似的问题上。

分布式:基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络编程的

类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。

健壮:Java的强类型机制(所有数据必须携带它的类型)、异常处理、垃圾的自动收集(释放垃圾占用

的内存空间,即把不在使用的空间标记为可用)等是Java程序健壮性的重要保证。对指针的包装是Java的明智选择。

引用->安全的指针(C中的指针可以计算,而引用是一个不可以改变的指针)

安全:Java提供一个安全机制以防恶意代码的攻击。所有的类必须经过类加载器的检查。

跨平台:.java的文件在Java平台上被编译为体系结构中立的字节码格式(.class的文件),可以在这个Java平台的任何系统中运行。实际上是JVM的跨平台。

性能好:与解释型的高级脚本语言相比,Java的性能较优。Java是编译型语言,可以将一个文件直接编

译成CPU可执行的文件,交给CPU执行,而解释型语言是逐行编译,逐行交给CPU执行,所以速度会慢。JVM内置了hotspot,可以抓出执行次数多的程序,将其转换成汇编语言,进而提升执行速度,可以接近C\C++的速度。

多线程:Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建,目的是最大

化利用CPU。可以同时执行多个任务,最大化利用CPU,提高吞吐量,应对用户多的情况。

Java源码的查看:jdk1.8.0_221->src->java->io,可以查看到java的各方法源码,如println方法等

1.3Java安装

JRE(Java运行环境)=JVM(Java虚拟机)+JavaSE标准类库

JDK(Java开发工具包)=JRE+开发工具集(如Java编译工具等)

安装:安装jgk与notepad++,放到一个目录下。

配置环境变量:控制面板->系统->高级系统配置->高级->配置环境变量,删除classpath变量,在系统变 量上配置PATH:java的bin目录的路径。

cmd窗口查看是否成功:javac -version;java -version.观察版本是否一致,一致即安装成功,javac其实 就是编译器。

开发并运行一个java程序的步骤:

        a)编写java源文件,在目录中先创建一个新的文本文件,改名为类目.java,编辑这个文件

        注意点:大小写敏感,标点符号必须是英文状态,缩进是tab

        b)对源文件进行编译,在命令行中,以刚才的目录为当前工作目录进行编译,

        命令:javac 空格 类名.java,编译后会生成一个文件“类名.class”,notepad++注意要配置格式为ANSI编码。另外若输入javac *.java,就会把当前目录下的所有java文件都编译一遍。

Java三部曲之JavaSE基础_第2张图片

c)运行编译好的字节码,还是以刚才的目录为当前工作目录进行运行,命令:java 类名

d)反编译字节码文件,以刚才的目录为当前工作目录进行运行,可以获得编译的Java语句是什么样的(包括缺省部分),命令:javap 类名

1.4第一段程序

// 注释:是一种说明型文字,这是单行注释
/*
这是多行注释,但是多行注释不能嵌套,可以先写注释的思路,再写代码
*/
/*
public是公共的意思,是一个修饰符,后面修饰的是类
class是类的意思,作用是定义一个类
Hello是类名
{}表示一个范围,类目后面的一对{}及其中的内容称为类体(body)
类=类名(类签名)+类体
类是java程序的基本单位,要想构成一个程序必须要有类。
*/
public class Hello{
/*
下面是一个方法的定义,方法是java程序的一个独立的功能单位。
public表示方法是公共方法
static也是修饰符,表示静态方法
void是空的意思,表示方法没有返回值
main是方法名
(String[] args)是方法的参数列表
参数后面的一对{}及其中的内容称为方法体
方法=方法头(方法签名)+方法体

这是主方法,入口方法,程序总是从主方法开始执行,主方法的写法是固定的,记住它:public static void main(String[] args)!!!

 有主方法的类称为主类,没有主方法的类称为非主类
 */
    public static void main(String[] args) {
    //下面是一个打印语句,语句是java的最小执行单位,语句必须以;为结束
        System.out.println("java程序,我来了。");
    }
    /*
    类中可以有多个方法,但是方法不能冲突

    下面这个方法不会执行,因为它不是入口方法(主方法)
    */
    public static void test(){
        System.out.println("test...");
    }
}
  • 练习
//写一个类PersonInfo将个人的基本信息(姓名、性别、籍贯、住址)打印在控制台上输出。
//各条信息分别写一行。加上一些注释。

public class PersonInfo{//定义类PersonInfo
    public static void main(String[] args){//定义主方法main
        System.out.println("姓名:xx");//语句1
        System.out.println("性别:男");//语句2
        System.out.println("籍贯:x省x市");//语句3
        System.out.println("住址:xxx");//语句4
    }
}
  • 注意

a)一段java程序的结构:类{//java程序的基本单位

                                                方法{//java程序的独立的功能单位

                                                        语句;//java程序的最小执行单位

                                                }

                                        }//类中可以包含多个方法,方法中可以包含多条语句。

b)修改源文件:修改.java的源文件,必须保存加编译,否则运行的仍然是之前编译的.class文件。

c)"{}"总是成对出现的,.java文件中可以包含多个并列的类,并且编译出的.class文件不是和.java文

件个数一样,而是和.java文件中的类的个数一样。

d)非主类不能运行,但是主类都可以执行,所以.java文件中的多个主类都能执行。

{java主类,执行过程:

        1)创建JVM

        2)把主类加载到JVM中执行

        3)执行主类中的入口方法(主方法)

        4)入口方法执行完毕后,销毁JVM

}

e)公共类:public修饰的类,公共类的类名必须与源文件名一致,这就导致一个源文件中只能有一个公共类,但一个源文件中也完全可以不写公共类。

第二章:Java基本语法

2.1关键字

特点:关键字都是小写字母。

2.1.1关键字介绍

用于定义数据类型的关键字:

{

class,interface,enum,byte,short,int,long,float,double,char,boolean,void

}

用于定义数据类型值的关键字:

{

true,false,null:

}

用于定义流程控制的关键字:

{

if,else,switch,case,default,while,do,for,break,continue,return

}

用于定义访问权限修饰符的关键字:

{

private,protected,public

}

用于定义类、函数、变量修饰符的关键字:

{

abstract,final,static,synchronized

}

用于定义类与类之间关系的修饰符:

{

extends,implements

}

用于定义建立实例及引用实例,判断实例的关键字:

{

new,this,super,instanceof

}

用于异常处理的关键字:

{

try,catch,finally,throw,throws

}

用于包的关键字:

{

package,import:

}

其他修饰符关键字:

{

native,strictfp,transient,volatile,assert

}

一些保留字(C++中的关键字,以后可能会用,所以最好不要命名这些保留字为标记符)

{

byValue,cast,future,generic,inner,operator,outer,rest,var,goto,const

}

2.2标识符

定义:凡是自己取名字的地方就是标识符(如变量、方法和类的命名)

2.2.1定义规则:

a)由英文字母大小写,阿拉伯数字,_与$组成(也尽可能少用),数字不能做开头,不能包含空格

b)不能用关键字与保留字,但可以包含关键字和保留字

c)严格区分大小写,长度无限制,最好用其有实际意义的标识符

d)命名规范:

{

包名:多单词组成时所有字母都小写:xxyyzz

类名、接口名:多单词组成时,所有单词的首字母大写:XxYyZz

变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始首字母大写:xxYyZz

常量名:所有字母都大写。多单词时每个单词用下划线连接:XX_YY_ZZ

}

2.2.2编程风格:

a)用文档注释整个类或方法,用单行或多行注释某个步骤

b)缩进用tab,运算符左右加一个空格:如2 + 4 * 5

c)左花括号{,置于行尾,不要换行

2.3变量

变量的概念:

{

a)内存中的一个存储区域(字节数),可以保存一些数据到该数据被修改之前,如x = 100,->,x=200

b)该区域有自己的名称(变量名)和类型(数据类型)

c)java的每个变量必须先声明,后使用,即:数据类型 变量名,int haha,变量名不能重复声明

d)该区域的数据可以在同一类型范围内不断变化(也就是说,数据不仅有类型限制,比如int型整数,还有范围限制,也就是存储区域的大小来决定,比如一千亿这种数据就不能放进去,这个区域大小就由数据类型来决定,不同数据类型的存储区域大小是不同的)

e)变量的作用只在一对{}之间有效,使用声明变量的那个{}内

f)变量的赋值:变量名 = 值

g)变量的取值:System.out.println(变量名);

}

常量介绍:就是字面量(就是字面上的量,比如10就是数值型的常量)和被final修饰的量,常量的内存空间不能被写入,如语句: 50 = 50;,显然是错误的,因为左边的50是常量,其内存空间不能被写入,也就不能被赋值了,所有=的左边必须是变量。

2.3.1按数据类型区分

决定了空间大小,以及里面的数据能做什么;我们尽可能采用满足需求的小内存数据类型

变量按数据类型来分:

1)基本数据类型:内存区域中保存的是数据本身

a)数值型:整数:{

byte ,1字节=8bit ,-128~127

short ,2字节,-32768~32767

int,4字节,-20多亿~20多亿

long,8字节,-2^63~2^63-1(按实际数位去存储)

}

浮点数:{

float,4字节,-10^38~10^38(单精度浮点数,存储方式是个位数,小数位,与

指数部分)

double,8字节,-10^308~10^308(双精度浮点数)

}

b)字符型:char,2字节

{

字符常量是用单引号括起来的单个字符,如'c','中','1',还有特殊的转

义字符,用\来转变其含义,如char c1 = '\n'; //'\n'表示换行。

常见转义字符:\b:退格符;\n:换行符;\r:回车符;\t:制表符;\":双引号;\':单引号;\\:反斜杠。

Unicode值表示字符型常量:'\uXXXX',其中XXXX代表一个十六进制整数,如:\u000a表示\n。

char类型可以通过其 Unicode码进行运算。

}

c)布尔型:boolean,1字节

{

只允许取值true和false,无null。

不可以0或非0来代表false和true。

}

2)引用数据类型:内存中保存的是别的数据的地址(内存中字节的编号,其实就是正整数,但是获取不到,这就是安全的不能更改的指针),所有的引用变量占用的内存空间总是一样的(64位jdk对应的是8字节,32位jdk对应的是4字节)。

值null可以赋值给任何引用类型(类、接口、数组)的变量,用以表示这个引用类型变量中保存的地址为空。

String:{

String类是一个典型的不可变类,String对象创建出来就不可能被改变。

创建出的字符串将存放在数据区,保证每个字符串常量只有一个,不会产生多个副本。

String s0 = "hello";

String s1 = "he" + "llo";

}

3)强制类型转换:范围大的数据类型转换成范围小的数据类型,boolean数据类型不能强转。

4)整数型字面量为int,但int型字面量可以赋值给short或byte型,浮点数字面量为double型,但编译器不能自动将其转换为float型赋值给float变量,又有非long整数经过运算,结果总是int型,含long整数进行运算,会变成范围大的long类型。

2.3.2按变量的声明位置来分

1)局部变量:声明在方法中的变量,范围小,寿命短

2)成员变量:声明在类中的方法外变量,范围大(全局),寿命长

2.3.3进制

283749:十进制数,对应位上数字乘以其权值

2*10^5+//权值 = 10的N次幂

8*10^4+

3*10^3+

7*10^2+

4*10^1+

9*10^0

10进制:数字是10个,'0'~'9',逢10进1

8进制:数字有8个,'0'~'7',逢8进1

16进制:数字有16个,'0'~'15',逢16进1,权值是16的N次幂,用0x开始就是16进制数

2进制:数字有2个,'0'~'1',逢2进1,权值是2的N次幂

比如:0x38

3*16^1+8*16^0=56

00101101

0*2^7+

0*2^6+

1*2^5+

0*2^4+

1*2^3+

1*2^2+

0*2^1+

1*2^0=45

             十进制                          二进制                        十六进制

                 0                               0000                                 0

                 1                               0001                                 1

                 2                               0010                                 2

                 3                               0011                                 3

                 4                               0100                                 4

                 5                               0101                                 5

                 6                               0110                                 6

                 7                               0111                                 7

                 8                               1000                                 8

                 9                               1001                                 9

                 10                             1010                                 A

                 11                             1011                                 B

                 12                             1100                                 C

                 13                             1101                                 D

                 14                             1110                                 E

                 15                             1111                                 F

一个16进制数正好可以对应4个比特(bit)

一个字节有8个比特, 一个字节可以用2个16进制数表示

        0x85C39A7D

        1000 0101 1100 0011 1001 1010 0111 1101

计算机底层所有数据都是以二进制的补码形式保存的,正数的补码就是本身,负数的补码就是它的相反数全部取反再加1得到的。

一个数是正数还是负数由符号位来决定,符号位总是在一个数的最高位,如果符号位为0表示正数,符号位为1表示负数。

0011 0101 -> 这是一个正数,补码还是自身

0x35 ->53,所以0011 0101是53在计算机中的存储

1101 0111 ->这是一个负数,并且是补码

-1=>1101 0110

取反=>0010 1001=>0x29=>41

所以1101 0111是-41在计算机中的真实存储

1110 1000 ->这是一个负数

-1=>1110 0111

取反=>0001 1000=>0x18=>24

所以1110 1000是-24在计算机中的存储

byte型数据的最大值,1字节=8bit,可装8个二进制数:

正数(绝对值最大),除了符号位,其他全为1:0111 1111=>0x7F=>127

正数(绝对值最小):0000 0000 0000 0000=>0x00=>0

负数(绝对值最大),除了符号位,其他全为0:1000 0000=>-1=>0111 1111=>取反=>

1000 0000=>0x80=>128=>-128

负数(绝对值最小):1111 1111=>1111 1110=>0000 0001=>1=>-1

short,int,long等类型如上所述。

char类型,2字节,且没有符号位,所以:

最大值:1111 1111 1111 1111=>0xFFFF

最小值:0000 0000 0000 0000=>0x0000

判断对错:

int i1 = 20;

short s1 = i1;(错,需要强转一下,因为int是4字节)

char c1 = 97;()

char c2 = '我' - '你';()

char c3 = (char)(c1 - c2);

float f1 = i1;

long l1 = 234234239933;

f1 = l1 * 20;

double d1 = .342;

d1 = i1 * f1 * l1;

l1 = f1 / 10000;

boolean b1 = (boolean)1;

  • 代码讲解
public class VariableTest{

    public static void main(String[] args){
        //变量是内存中的一块区域,区域中可以保存数据
        //声明:数据类型 变量名
        int var1;//在内存中开辟一块空间(4字节),空间中可以保存整数
        //变量的赋值
        var1 = 10;//把10这个值写入var1变量名代表的内存空间中

        System.out.println(var1);

        int var2 = var1;//此时要先读(从var1的存储区域读数据,其实就是copy一个副本出来)var1的值,再将读出的值赋给(写入var2的存储区域)var2
        System.out.println(var2);

        var1 = var1 * var2;//赋值(写入)操作,从右向左的计算,先读取var1与var2的值,再计算var1*var2,将计算结果赋值给var1,写入var1的同时,抹除var1之前保存的数据
        //右边的值必须是确定的时候,才能给左边的变量写入
        System.out.println(var1);

    }
}

public class VariableTest{
//浮点数字面量默认为double型,整数字面量默认int型
    public static void main(String[] args){
        int b1;
        byte i1;
        short s1;    
        b1 = 10;//初始化赋值,就是声明好后立刻赋的值,不赋值就会报错
        int b2 = 10;//这样最好,可以声明的同时初始化赋值
        s1 = 10;
        i1 = 20;//字面量20是默认为int型,但是保存到了byte变量中,是因为编译器可以自己检查该常量是否在byte保存的范围内,这是整数的特殊待遇
        b1 = i1;//内存小的byte类型可以写入内存的大的int类型
        i1 = b1;//反过来就不可以,但可以通过如下操作进行强制类型转换
        i1 = (byte)b1;//强制将b1转换成byte型,就可以赋值给byte类型的变量了,但是如果超出byte的存储范围,数据将会按二进制损失
        long l1 = 5000000000L;
        //直接写5000000000会出错,因为字面量默认用int型保存,其存储上限是20亿
        //又因为int类型本来就放不下50亿,所以就不能出现50亿的字面量,也就无法进行强制类型转换,只能加后缀L声明其数据类型为long
        float f1 = (float).32;
        //此处右边是double型,但编译器不会自动帮忙转换类型,不过.32是可以存进double型的空间内的,所以可以强制类型转换    
        l1 = f1;//虽然l1是8字节,而f1是4字节,但是f1的取值范围大,所以也不能兼容
        //事实上,范围大的值都不能赋值给范围小的变量,其实和内存反而没有关系
        f1 = l1;//所以l1虽然8字节,但是可以赋值给4字节的f1,因为f1范围更大 
        //兼容性排序:byte
  • 代码讲解2
class CharTest2{

    public static void main(String[] args){

        char c1 = 'a';

        char c2 = 10;
        char c3 = '\n';//换行,其Unicode码为10

        char c4 = '\r';//回车
        char c5 = 13;

        char c6 = '\t';//制表符TAB
        char c7 = 9;

        System.out.println(c2);
    }

}


public class CharTest{

    public static void main(String[] args){
        //char占用2个字节,字面量用''括起来
        char c1 = 'a';//每个字符都有一个唯一的Unicode码,实际上'a'中存储的就是a对应Unicode码,也就是97,在“编辑”->“字符面版”中可以查看每个字符的码值
        //就可以将'a'的Unicode码存入变量c1中
        char c2 = 'b';
        char c3 = 'A';
        char c4 = '2';
        char c5 =  '我';
        char c6 =  '你';
        char c7 = 100;//可以直接用Unicode码值给char类型赋值,还省得查表了,更方便
        System.out.println(c1);//打印时,是先读取c1存储的Unicode码值,再将其翻译成对应的字符
        System.out.println((int)c1);//此时可以直接打印字符的码值
        System.out.println(c2);
        System.out.println(c3);
        System.out.println((int)c3);//事实上,小写字母的码值比大写字母的码值大32
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);//快捷键ctrl+D:复制上一行,ctrl+L:删除上一行
        System.out.println(c7);//打印出d,就是Unicode码值100对应的字符,其实char就是整数,因为它就是存储的整数码值,其存储范围是0~65355

        c7 = (char)(c7 + 20000);//c7+20000是int型,其范围比char大,所以不能直接赋值给char型,只能强转为char型再赋值
        System.out.println(c7);

        System.out.println((int)'a');//练习
        System.out.println((int)'b');
        char c8 = 'c';
        c8 = (char)(c8 + 500);
        System.out.println(c8);
        char c9 = '我' - '你';//是对的,没有变量参与,常量的运算且结果为正数,编译器会将其优化成Unicode码
        System.out.println(c9);

    }
}
public class BooleanTest{

    public static void main(String[] args){
        //boolean只占用一个字节,并且只允许使用true和false赋值
        boolean b1 = true;
        boolean b2 = false;
        boolean b3 = b1;

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);

        //boolean b4 = (boolean)1;//int类型不能强转为boolean类型,实际上只有数值型和char类型之间可以强转

    }
}
class Xinxi{

    public static void main(String[] args){

        String name = "姓名:xxx";
        char symbol = ',';
        String age1 = "年龄:";
        int age2 = 23;
        String address = "地址:xx省xx市";
        String xinxi = "";
        xinxi = name + symbol + age1 + age2 + symbol +address;
        System.out.println(xinxi);
    }
}

public class StringTest{
    public static void main(String[] args){

        String s1 = "abc";
        String s2 = null;//空地址,表示没有字符串
        String s3 = "";//空串,表示有一个字符串,但是字符串里没字符

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);

        //String可以与任意数据类型做拼接,拼接结果是一个新的字符串,内容是拼接后的新内容

        s1 = s1 + 100;//"abc100"
        System.out.println(s1);

        String s4 = "xxx";
        s4 = s4 + false + 10 + 3.22 + "hhhh" + 'a';//"xxxfalse103.22hhhha"
        System.out.println(s4);

        int n = 927;
        //String s5 = n;//会报错,因为不能直接把int型数据赋值给String类型
        //事实上,只需要加一个空串进行处理就行了
        String s5 = "" + n;//添加空串就可以把基本数据变成字符串(因为有字符串参与的拼接运算结果还是字符串)
        System.out.println(s5);


        String s6 = "248";
        int n1 = Integer.parseInt(s6);//要想把字符串转换成真的整数,就要用这个函数进行转换
        System.out.println(n1);

        String s7 = "345";
        s7 = s7 + 3 + 4;//"34534"
        s7 = 3 + 4 + s7;//"734534"因为运算符是从左向右计算的,=左右是从右向左运算
        s7 = 'a' + 1 + s7;//'a'的码值97+1=98,再加"734534"得到"98734534"
        System.out.println(s7);
    }
}
  • 代码讲解3
public class BinaryTest{

    public static void main(String[] args){

        byte b1 = (byte)0x80;//0000 0000 0000 0000 0000 0000 1000 0000(直接写0x80是int型,有4字节,写作上面的情况,byte类型只有一个字节,接不住)
        //byte强转后,直接扔掉高位的三个字节,变成1000 0000
        byte b2 = 0x7F;
        System.out.println(b1);
        System.out.println(b2);

        short s1 = (short)0x8000;//0000 0000 0000 0000 1000 0000 0000 0000
        short s2 = 0x7FFF;
        System.out.println(s1);
        System.out.println(s2);

        int i1 = 0x80000000;//字面量就是int型,可以直接赋值
        int i2 = 0x7FFFFFFF;
        System.out.println(i1);
        System.out.println(i2);

        long l1 = 0x8000000000000000L;//字面量int型存不下这个值,要结尾加L声明为long型
        long l2 = 0x7FFFFFFFFFFFFFFFL;
        System.out.println(l1);
        System.out.println(l2);

        char c1 = 0x0000;
        char c2 = 0xFFFF;
        System.out.println((int)c1);//System.out.println(c1);是null,所以打印为空
        System.out.println((int)c2);//System.out.println(c2);会打印?,因为65535码值对应的字符不是汉字,中文操作系统不能识别

    }
}
  • 练习
public class Homework1{
    \\打印20*8的矩形;
    public static void main(String[] args){
        System.out.println("********************");
        System.out.println("********************");
        System.out.println("********************");
        System.out.println("********************");
        System.out.println("********************");
        System.out.println("********************");
        System.out.println("********************");
        System.out.println("********************");
    }
}

class SanJiaoXing{
    \\打印高度为5的等腰三角形。 
    public static void main(String[] args){
        System.out.println("    *");
        System.out.println("   ***");
        System.out.println("  *****");
        System.out.println(" *******");
        System.out.println("*********");
    }
}


public class Variable{

    public static void main(String[] args){

        double var1 = 10;
        double var2 = 2.0;

        String s1 = var1 + "/" + var2 + "=";
        var2 = var1 / var2;
        s1 = s1 + var2; //最好的办法就是抓住当下时间的现值

        System.out.println(s1);
    }
}

2.4运算符

2.4.1 位运算符

左移:

3 3*2*2=12 0000 0011 --> 0000 1100

右移:>>(右移一位相当于除以2,但右移前最高位是0则最高位补0,是1则最高位补1)

3>>1 --> 3/2=1

0000 0011 --> 0000 0001

-1>>2 = -1-->1111 1111 1111 1111 1111 1111 1111 1111-->右移2位还是-1

无符号右移:>>>(直接将二进制码右移,包括符号位)

-1>>2 -->1111 1111 1111 1111 1111 1111 1111 1111-->0011 1111 1111 1111 1111 1111 1111 1111=一个很大的正数

与:&(结果总是小于等于运算数)

6&3=2

0000 0110

0000 0011 --> 0000 0010 = 2

或:|(结果总是大于等于运算数)

6|3=7

0000 0110

0000 0011 --> 0000 0111 = 7

位或:^(相同为0,不同为1,结果不固定,但是重新位运算一次可以恢复,常用来加密与解密)

6^3=5,5^3=6

0000 0110

0000 0011 --> 0000 0101 = 5

取反:~(结果是相反数减1)

~6=-7

0000 0110 --> 1111 1001 = -7

public class BinaryTest{

    public static void main(String[] args){

        byte b1 = (byte)0x80;//0000 0000 0000 0000 0000 0000 1000 0000(直接写0x80是int型,有4字节,写作上面的情况,byte类型只有一个字节,接不住)
        //byte强转后,直接扔掉高位的三个字节,变成1000 0000
        byte b2 = 0x7F;
        System.out.println(b1);
        System.out.println(b2);

        short s1 = (short)0x8000;//0000 0000 0000 0000 1000 0000 0000 0000
        short s2 = 0x7FFF;
        System.out.println(s1);
        System.out.println(s2);

        int i1 = 0x80000000;//字面量就是int型,可以直接赋值
        int i2 = 0x7FFFFFFF;
        System.out.println(i1);
        System.out.println(i2);

        long l1 = 0x8000000000000000L;//字面量int型存不下这个值,要结尾加L声明为long型
        long l2 = 0x7FFFFFFFFFFFFFFFL;
        System.out.println(l1);
        System.out.println(l2);

        char c1 = 0x0000;
        char c2 = 0xFFFF;
        System.out.println((int)c1);//System.out.println(c1);是null,所以打印为空
        System.out.println((int)c2);//System.out.println(c2);会打印?,因为65535码值对应的字符不是汉字,中文操作系统不能识别

    }
}

class BinaryTest1{

    public static void main(String[] args){

        int n1 = 10;
        int n2 = 0x10;
        //int n3 = 08;// 0开始表示8进制。
        n1 = n1 << 3;
        System.out.println(n1);
        System.out.println(n2 >> 2);
        System.out.println(-1 >> 5);
        System.out.println(-1 >>> 1);

    }
}

class BinaryTest2{

    public static void main(String[] args){

        int n1 = 0x7C;
        int n2 = 0x9B;
        //分析过程
        int n1 = 0xA5;//1010 0101
        int n2 = 0xC7;//1100 0111
        //0000 0000 0000 0000 0000 0000 1010 0101
        //0000 0000 0000 0000 0000 0000 1100 0111
        //0000 0000 0000 0000 0000 0000 1000 0101 = 0x85 = 133
        System.out.println(n1 & n2);
        //0000 0000 0000 0000 0000 0000 1010 0101
        //0000 0000 0000 0000 0000 0000 1100 0111
        //0000 0000 0000 0000 0000 0000 1110 0111 = 0xE7 = 231
        System.out.println(n1 | n2);
        //0000 0000 0000 0000 0000 0000 1010 0101
        //0000 0000 0000 0000 0000 0000 1100 0111
        //0000 0000 0000 0000 0000 0000 0110 0010 = 0x62 = 98
        System.out.println(n1 ^ n2);
        //0000 0000 0000 0000 0000 0000 1010 0101 => ~取反
        //1111 1111 1111 1111 1111 1111 0101 1010 => -1
        //1111 1111 1111 1111 1111 1111 0101 1001 => 取反
        //0000 0000 0000 0000 0000 0000 1010 0110 => 0xA6 => -166
        System.out.println(~n1);

    }
}

2.4.2 算术运算符

+ 正号 +3 3

- 负号 b=4;-b -4

+ 加 5+5 10

- 减 6-4 2

* 乘 3*4 12

/ 除 5/3 1

(如果是两个整数相除,结果总是整数,并且不会四舍五入,而是直接把小数丢弃)

% 取模(余) 7%5 2

(n%m,如果n%m结果为0,说明n能被m整除,如果n%2结果为0,说明n是偶数,结果不为0,说明是奇数;

n%m的结果一定是小于m的。)

  • 如果对负数取模,可以把模数负号忽略不计,如:5%-2=1。但被模数是负数则不可忽略。此外,取模运算的结果不一定总是整数。
  • 对于除号"/",它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而而舍弃小数部分。如:int x = 3510;x = x / 1000 * 1000;//x的结果是3000。注意,整数型的x/0会报错,浮点数的0.0/0.0会返回Nan,5.0/0.0返回Infinity。
  • “+”除字符串相加功能,还能把非字符串转换成字符串。如:System.out.println("5+5="+5+5);//打印结果是5+5=55

System.out.println('*' + '\t' + '*');//三个非long型相加,得到的int型,这里是ASCII码值的和(int型)

System.out.println("*" + '\t' + '*');//含字符串的相加,“+”是拼接符,

单目运算符

++ 自增(前):先运算后赋值 a=2;b=++a; a=3;b=3

++ 自增(后):先赋值后运算 a=2;b=a++; a=3;b=2

-- 自减(前):先运算后赋值 a=2;b=--a; a=1;b=1

-- 自减(后):先赋值后运算 a=2;b=a--; a=1;b=2

+ 字符串相加 "He"+"llo" "Hello"

public class OperatorTest {

    public static void main(String[] args){

        int n = 10;
        n = n++;//先用后加,可以理解n被刷过两次值,因为赋值往往是后进行的,而先用这一步,又要用到n的初始值,
        //所以第一次n++把n刷成值11,接着把这个值存储在临时空间里等待赋值结束释放,第二次用n的初始值赋给左边的n,此时把n又刷成了10
        System.out.println(n);

        int m = 10;
        m = ++m;//先加后用,这里不需要考虑赋值的顺序,因为此处的赋值就是正常顺序进行的,直接把m先刷成11,然后直接赋给m,m还是11
        System.out.println(m);

        int a = 1;
        int b = 2;
        int c = 3;

    }
}

2.4.3赋值运算符

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

java支持连续赋值,如:a=b=c=100;//结果是a,b,c三个变量中的值都是100

  • short s = 3;

s = s + 2;//会引起数据类型变化,short型+2变成了int型,赋值给short型变量s,会出问题

s += 2;//累积操作符,不会引起数据类型变化,不会出问题

  • int i = 1;

i *= 0.1;//i是int型,int型累计运算,数据类型不变,还是int型,1*0.1=0.1,int型是0。

System.out.println(i);

2.4.4比较运算符

  • 比较运算符:==,!=,,=,instanceof(检查是否是类的对象,如"Hello" instanceof String:true)
  • ==,!=可以适用于所有数据类型,比大小的只适用于数值型;注意有的类型不能比较==,比如不同的引用类型。
  • 其结果都是boolean型,也就是要么是true,要么是false。

2.4.5逻辑运算符

逻辑运算符用于连接布尔型表达式,在Java中不可以写成33 & x

&与&&的区别:

逻辑与&时,左边无论真假,右边都进行运算

短路与&&时,如果左边为真,右边参与运算,如果左边为假,右边就不再运算

逻辑或|与短路或||的区别同理;

异或(^)与或(|)的不同:当左右都为true时,结果为false,异或追求的就是“异”时为true。

int x = 1;
int y = 1;
if (x++ == 2 & ++y == 2)//x++ == 2,先对x++进行运算,x++的运算方式是先保存下x的值,再对x进行自增1
//做==判断时,利用x保存的值1与2进行比较,x == 2,判断为False,但是x此时已经自增1,变成了2了,由于逻辑与&
//所以右边也必须运算,也就是++y得到y的值为2,y == 2,判断为True,最后逻辑与False & True,结果为False
{
    x = 7;//故不执行此处
}
System.out.println("x="+x+",y="+y);//打印出x=2,y=2

2.4.6三元运算符

  • 格式:

(条件表达式)?表达式1:表达式2;//当条件表达式为True,运算后的结果是表达式1;为False时,运算后的结果是表达式2;

表达式1和表达式2为同种数据类型

  • 三元运算符与if-else的联系与区别:

三元运算符可简化if-else语句

三元运算符要求必须返回一个结果

if后的代码块可有多个语句

2.4.7命令行参数

//找出三个数中的最大值
class OperatorTest3 {
    //java OperatorTest3 11 22 33;返回22
    public static void main(String[] args){

        int n1 = Integer.parseInt(args[0]);
        int n2 = Integer.parseInt(args[1]);
        int n3 = Integer.parseInt(args[2]);

        int temp = n1 > n2 ? n1 : n2;
        int max = temp > n3 ? temp : n3;
        System.out.println("max = " + max);
    }
}

class ArgsTest {

    public static void main(String[] args) {

        //命令行参数:跟在命令后面的一系列字符串,作用是给命令传递详细的数据
        //把命令行参数的字符串转成整数

        int n1 = Integer.parseInt(args[0]);//把命令行参数中的第一个字符串转成真的整数
        int n2 = Integer.parseInt(args[1]);//把命令行参数中的第二个字符串转成真的整数

        System.out.println(n1);
        System.out.println(n2);
    }
}

2.4.8运算符优先级

优先级是指在表达式中的运算顺序,只有单目运算符、三元运算符、赋值运算符是从右向左运算的。

. () {} ; ,

R>>L

++ -- ~ !(data type)

L>>R

* / %

L>>R

+ -

L>>R

> >>>

L>>R

< > = instanceof

L>>R

== !=

L>>R

&

L>>R

^

L>>R

|

L>>R

&&

L>>R

||

R>>L

? :

R>>L

= *= /= %=

+= -= >=

>>>= &= ^= |=

2.5程序流程控制

2.5.1顺序结构

程序从上到下执行,没有判断和跳转

2.5.2分支结构

根据条件,选择性执行某段代码

有if-else和switch两种分支语句

if-else语句:

if (条件表达式1){

        执行代码块1;

} else if (条件表达式2){

        执行代码块2;

} else {

        执行代码块3;

}//其余以此类推

if (y > 2)

        System.out.println("打印此句");

         System.out.println("不打印不相邻的语句");

else

        System.out.println("不带{}时,else与if一样,只打印相邻语句");

public class IfTest {

    public static void main(String[] args){
        //条件分支结构
        int n = Integer.parseInt(args[0]);

        if (n == 10) {
            System.out.println("n == 10");
        } else if (n == 20) {
            System.out.println("n == 20");
        } else if (n >= 15) {
            System.out.println("n >= 15");//当输入n=20时,只会输出上面一个分支,因为分支结构,只输出一个值
        } else {
            System.out.println("n != 10 & n != 20");
        }
    }
}

class IfExer {
    public static void main(String[] args){

        int scores = Integer.parseInt(args[0]);

        if (scores > 100 || scores < 0){
            System.out.println("不可能");//放在开头可以首先排除不合法分数
        } else{//用else不用else if语句可以节省资源
            if (scores == 100){
                System.out.println("奖励一辆BMW");
            } else if (scores > 80 && scores <= 99){
                System.out.println("奖励一台iphoneX");
            } else if (scores >= 60 && scores <= 80){
                System.out.println("奖励一本参考书");
            } else{
                System.out.println("什么也没有");
            }
        }
    }
}

switch语句是一个条件表达式为等式的简化写法:

switch(变量){

case 常量1:

        语句1;

        break;

case 常量2:

        语句2;

        break;

default:

        语句;

        break;

}

public class SwitchTest{

    public static void main(String[] args){

        /*
        switch(变量){//必须是变量,数据类型必须是非long整数,字符串,枚举
        case 常量1://if (变量 == 常量1),常量就是字面量或用final修饰的量
            语句1;
            break;//跳出整个switch,否则会继续执行下面的语句
        case 常量2://if (变量 == 常量2)
            语句2;
            break;
        default: //else
            语句3;
            break;
        }
        */
        int n = 2;

        switch(n){
        case 1:
            System.out.println("n == 1");
            break;
        case 2:
            System.out.println("n == 2");
            break;
        default:
            System.out.println("default");
            break;
        }
    }
}

class SwitchExer{

    public static void main(String[] args){

        char ch = args[0].charAt(0);//接受命令行参数的字符方法
        String str = "other";//先声明一个空字符串,并赋初值作为备选值,即case都不满足的情况下的返回值

        switch(ch){
            case 'a'://直接case穿透就行
            case 'b':
            case 'c':
            case 'd':
                ch -= 32;//小写字母减32可以得到大写字母,码值相减,-=不发生数据类型变化
                //注意,ch = ch - 32;的话,会变成int型,需要进行char强转才行,累积运算符则不会
                str = "" + ch;//空字符串拼接ch,得到内容为ch的字符串
        }
        System.out.println(str);//若没有触发case,返回"other",否则返回"ch"的值
    }
}
  • 练习
class HomeWork {
    //嫁不嫁问题
    public static void main(String[] args) {

        int height = Integer.parseInt(args[0]);
        int money = Integer.parseInt(args[1]);
        boolean handsome = Integer.parseInt(args[2]) != 0;//对后面的表达式进行判断得到的布尔值赋给handsome

        if (height > 180 && money > 10000000 && handsome){
            System.out.println("我一定要嫁给他!");
        } else if (height > 180 || money > 10000000 || handsome){
            System.out.println("嫁吧,比上不足比下有余。");
        } else {
            System.out.println("不嫁!");
        }
    }
}

class HomeWork2 {

    public static void main(String[] args){

        int month = Integer.parseInt(args[0]);

        switch (month) {
        case 1://多值穿透合并
        case 2:
        case 12:
            System.out.println("冬季");//1,2,12月是冬季
            break;
        case 3:
        case 4:
        case 5:
            System.out.println("春季");//case可以多次重复,执行下面同一条语句
            break;
        case 6:
        case 7:
        case 8:
            System.out.println("夏季");
            break;
        case 9:
        case 10:
        case 11:
            System.out.println("秋季");
            break;
        default:
            System.out.println("非法输入");
            break;
        }
    }
}
class HomeWork3 {

    public static void main(String[] args){

        //接收三个命令行字符串并转换为整数分别存入变量num1,num2,num3,对他们进行排序
        int num1 = Integer.parseInt(args[0]);
        int num2 = Integer.parseInt(args[1]);
        int num3 = Integer.parseInt(args[2]);
        //在有多种情况出现的时候,最好采用一次又一次递进的if-else结构进行嵌套,不容易混乱,用&&这种逻辑运算符,容易出现漏洞,另外最好只用<来判断,不容易混乱
        if (num1 < num2){//1<2
            if (num2 < num3){//1<2,2<3
                System.out.println(num1 + "," + num2 + "," + num3);
            } else if (num1 < num3) {//1<2,3<2,1<3
                System.out.println(num1 + "," + num3 + "," + num2);
            } else {//1<2,3<2,3<1
                System.out.println(num3 + "," + num1 + "," + num2);
            }
        } else {//2<1
            if (num1 < num3){//2<1,1<3
                System.out.println(num2 + "," + num1 + "," + num3);
            } else if (num2 < num3) {//2<1,3<1,2<3
                System.out.println(num2 + "," + num3 + "," + num1);
            } else {//2<1,3<1,3<2
                System.out.println(num3 + "," + num2 + "," + num1);
            }
        }
    }
}
class HomeWork5 {

    public static void main(String[] args){

        //接收命令行参数年月日,判断这一天是当年的第几天
        //年不重要,日也不重要,月最重要
        //判断一年是否是闰年的标准:可以被4整除,但不可被100整除;可以被400整除
        int year = Integer.parseInt(args[0]);
        int month = Integer.parseInt(args[1]) - 1;
        int day = Integer.parseInt(args[2]);
        int days = 0;


        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
            switch(month) {
            case 0:
                days = day;
                break;
            case 1:
                days = 31 * month + day;
                break;
            case 2:
            case 3:
                days = 31 * month + day - 2;
                break;
            case 4:
            case 5:
                days = 31 * month + day - 3;
                break;
            case 6:
            case 7:
            case 8:
                days = 31 * month + day - 4;
                break;
            case 9:
            case 10:
                days = 31 * month + day - 5;
                break;
            case 11:
                days = 31 * month + day - 6;
                break;
            default:
                System.out.println("不合法的输入");
                break;
            }
        } else {
            switch(month) {
            case 0:
                days = day;
                break;
            case 1:
                days = 31 * month + day;
                break;
            case 2:
            case 3:
                days = 31 * month + day - 3;
                break;
            case 4:
            case 5:
                days = 31 * month + day - 4;
                break;
            case 6:
            case 7:
            case 8:
                days = 31 * month + day - 5;
                break;
            case 9:
            case 10:
                days = 31 * month + day - 6;
                break;
            case 11:
                days = 31 * month + day - 7;
                break;
            default:
                System.out.println("不合法的输入");
                break;
            }
        }
        System.out.println(days);
    }
}
class HomeWork6 {
    //求当年的天数的改进写法
    public static void main(String[] args) {
        int year = Integer.parseInt(args[0]);
        int month = Integer.parseInt(args[1]);
        int day = Integer.parseInt(args[2]);
        int days = day;//初值就是散的天数

        switch (month) {
        case 12:
            days += 30;//累加上月份的天数
        case 11:
            days += 31;
        case 10:
            days += 30;
        case 9:
            days += 31;
        case 8:
            days += 31;
        case 7:
            days += 30;
        case 6:
            days += 31;
        case 5:
            days += 30;
        case 4:
            days += 31;
        case 3:
            days += 28;
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {//在这里判断是否为闰年,是的话就加一天
            days++;
        }
        case 2:
            days += 31;//若是1月,则直接输出days = day即可

        }
        System.out.println(year + "年" + month + "月" +  day + "日是" + "该年度的第" + days + "天");
    }
}

2.5.3循环结构

根据循环条件,重复性地执行某段代码

有while、do-while(这俩不指定循环次数)、for(指定循环次数)三种循环语句

注:JDK1.5提供了for each循环,方便地遍历集合、数组元素。

  • 循环语句的四个组成部分

初始化部分

循环条件部分//满足循环条件即进行循环

循环体部分//所谓循环就是反复执行循环体部分的代码

迭代部分//使循环条件趋向于假,没有迭代部分,就是无限循环

public class LoopTest {

    public static void main(String[] args){

        /*
        初始化语句;//循环因子:与循环条件和迭代部分有关的因子
        while (布尔循环条件) {
        循环体;
        迭代部分;
        }
        */
        int n = Integer.parseInt(args[0]);
        int result = 0;

        int i = 0;//循环因子i,初始化条件
        while (i < n) {//循环次数:条件右面的值-初值,有=再加1
            result += i;//循环体
            i++;//迭代语句
        }
        System.out.println("result = " + result);
        System.out.println("i = " + i);
    }
}

class LoopTest2 {

    public static void main(String[] args){
        /*
        初始化语句;
        do {
        循环体;
        迭代语句;
        } while (循环条件);
        */
        int n = Integer.parseInt(args[0]);
        int result = 0;

        int i = 0;
        do {
            result += i;
            i++;
        } while (i < n);
        System.out.println("result = " + result);
        System.out.println("i = " + i);
    }
}
class LoopTest3 {

    public static void main(String[] args) {
        /*
        for (初始化语句A;循环语句B;迭代语句C) {
        循环体D;
        }
        A B D C B D C B D …… B不再为True,结束循环
        显然,迭代语句C是每一次循环的开始
        */
        int n = Integer.parseInt(args[0]);
        int result = 0;

        for (int i = 0;i < n;i++) {
            result += i;
        }
        System.out.println("result = " + result);
    }
}
class LoopTest4 {

    public static void main(String[] args) {
        //无限循环的写法
        boolean loopFlag = true;//注意要有布尔变量去设置true
        while (loopFlag) {//注意如果用true直接放进条件中去,执行会报错,用布尔变量就不会报错
            System.out.println("while1");
        }
        System.out.println("after loop");
        do {
            System.out.println("do while");
        } while (loopFlag);
        System.out.println("after loop2");

        //for (int i = 0;i < 100;i--)//i--最多减到负数中的1000 0000 0000 0000 0000 0000 0000 0000,再减1就会变成0111 1111 1111 1111 1111 1111 1111 1111变成很大的正数,结束循环
        /*
        for (int i = 0;i < 100; )//这是死循环,因为i的值不会改变
        for (int i = 0; ;i++)//没有条件,就跳不出循环,这也是死循环
        for (int i =0;true;i++)//条件为true,也是死循环
        for ( ; ; )//什么都没有,也是死循环
        */
    }
}
  • 练习
class LoopWork {

    public static void main(String[] args){
        //打印n*8的矩形
        int n = Integer.parseInt(args[0]);
        int i = 0;
        while (i < n) {
            System.out.println("********");
            i++;
        }
    }
}

class LoopWork2 {

    public static void main(String[] args){
        //求100以内所有能被3整除的数的平均值
        int sum = 0;
        int count = 0;

        for (int i = 0;i < 100;i++){

            if (i % 3 == 0){
                sum += i;//有条件累加
                count++;//有条件计数
            }
        }
        //统计运算(求和,计数,最值,均值)结果只有一个,必须在循环结束以后输出
        int avg = sum / count;
        System.out.println("avg = " + avg);
    }
}
class LoopWork5 {

    public static void main(String[] args) {

        int n = Integer.parseInt(args[0]);

        for (int i = 0;i < n;i++){//外循环控制行
            for (int j = 0;j < i;j++) {内循环控制列,正斜率,*在增加
                System.out.print("*");
            }
            for (int j = 0;j < n - i;j++) {内循环控制列,负斜率,#在减少
                System.out.print(" ");
            }
            System.out.println();
        }
    }
}


class LoopWork6 {

    public static void main(String[] args){

        //找出100以内的所有质数
        for (int j = 2;j < 100;j++) {//j从2循环到99
            //判断j是否是质数,即只能被1和自身整除的数
            boolean flag = true;//假设j是质数,找反例推翻它更容易
            //从2~j-1中找任意数,若n被某个任意数整除了,它就不是质数
            for (int i = 2;i < j;i++) {//从2到j-1的遍历循环
                if (j % i == 0) {//尝试让j被i整除
                    flag = false;//若被整除了,推翻假设,flag刷为false
                }
            }
            //若没被推翻,则flag保持为true
            if (flag) {
                System.out.println(j + "是质数");
            }
        }
    }
}



class LoopExer {
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        //打印高为n的空心等腰三角形
        for (int i = 0;i < n;i++) {
            for (int j = 0;j < n - i - 1;j++) {
                System.out.print(" ");
            }
            for (int j = 0;j < 2 * i + 1;j++) {
                //if (第一行或最后行或第一列或最后列)
                if (i == 0 || i == n - 1 || j == 0 || j == 2 * i) {
                    System.out.print("*");
                } else {
                    System.out.print(" ");
                }
            }
            System.out.println();
        }
    }
}

2.5.4特殊流程控制语句

  • break语句(内在逻辑就是,一旦出现某种条件,立即终止执行某部分语句)

用于中断某个语句块(用{}修饰的语句块)的执行,包括case,循环语句,以及其他语句块。

class LoopTest7 {

    public static void main(String[] args) {

        l1 : for (int i = 0;i < 10;i++) {
            l2 : for (int j = 0;j < 5;j++) {
                System.out.println("j : " + j);
                if (j == 2) {
                    //break;//中断离它最近的循环l2
                    break l1;//也可以选择中断l1,此时直接输出j:0,j:1,j:2,即结束程序
                }
            }
            System.out.println("i : " + i);//此时没有机会执行本语句
        }
    }
}
  • continue语句

continue语句用于跳过某个循环语句块的一次执行(内在逻辑就是出现某种条件时,不执行此情况)。

continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环。

class LoopTest8 {
    public static void main(String[] args) {
        //continue中断当次循环,直接跳入下一次循环(跳到迭代语句处)
        l1 : for (int i = 0;i < 10;i++) {
            l2 : for (int j = 0;j < 5;j++) {
                if (j % 2 != 0) {
                    //continue;//触发continue后,中断最近的循环,后面的语句本次就不会执行了
                    continue l1;//也可以直接指定跳过的循环
                }
                System.out.println("j : " + j);//实际上只能打印出10次j:0
            }
            System.out.println("i : " + i);
        }
    }
}

class LoopWork8 {
    //用continue找质数
    public static void main(String[] args) {

        l1 : for (int i = 2;i < 10;i++) {
            for (int j = 2;j < i;j++) {
                if (i % j == 0) {//出意外了,不是质数,暗含了布尔变量
                    continue l1;//就要跳过这次输出,把所有不是质数的i都排除出去
                }
            }
            System.out.println(i + "是质数");//不出意外的话就打印出质数
        }
    }
}
  • return语句(结束整个方法,如main方法)

不是专门用来结束循环的,它的功能是结束一个方法。当一个方法执行到一个return语句时,这个方法将被结束。

与break和continue不同的是,return直接结束整个方法,不管这个return处于多少层循环之内。

  • 比较

break只能用于switch与循环中;

continue只能用于循环中;

break,continue后面不能有任何语句,因为一旦执行break或continue会理解跳出,永远执行不到该语句,所以会直接报错

2.6方法

2.6.1方法介绍

  • 什么是方法(函数)

方法是类或对象行为特征的抽象,也称为函数

Java里的方法不能独立存在,所有的方法必须定义在类里。且方法不可以嵌套。

  • 格式

修饰符 返回值类型 方法名(参数类型1 形参1,参数类型2 形参2,……) {

        程序代码;

        return 返回值;

}

其中:

形式参数:在方法被调用时用于接收外部传入的数据的变量。

参数类型:就是该形式参数的数据类型。

返回值:方法在执行完毕后返还给调用它的程序的数据。

返回值类型:方法要返回的结果的数据类型。

实参:调用方法时实际传给函数形式参数的数据。

/*
方法(method)是某种功能的体现,也称为函数
方法必须定义在类中,方法不可嵌套

修饰符 返回值类型 方法名(参数类型1 形参1,参数类型2 形参2,……) {
程序代码;
return 返回值;
}

方法 = 方法头(方法签名,相当于方法的功能说明书API)+方法体

方法的调用:方法名(实际参数1,实际参数2);  可以反复调用,但实参列表必须对照形参列表
方法的返回值接收:就是方法调用本身,只有调用时这一次机会,一旦错过就销毁了

如果方法没有返回值,使用void来表示返回值类型
如果方法没有形参,参数列表为空,但不能省略()

参数是方法的功能的输入数据,可以有多个
返回值是方法的功能的输出数据,最多有一个

关于方法的调用,从哪里调用的,调用完就返回到哪里继续执行
*/

public class MethodTest {
    //自定义方法,方法之间必须是并列
    //修饰符 返回值类型 方法名(参数类型1 形参1,参数类型2 形参2,……)
    public static int add(int a,int b) {
        System.out.println("add method");
        int c = a + b;//c是局部变量
        return c;
    }

    public static void hello() {//无形参
        System.out.println("hello……");
    }

    public static void main(String[] args) {//执行入口方法
        System.out.println("main begin");
        add(10,20);//调用add方法,此时会执行add方法,结果是打印"add method"
        int x = add(10,20);//方法调用本身add(10,20),就是返回值c,将其赋给x即可。
        hello();//调用也就无实参,且无返回值就不能接收返回值

        System.out.println(add(10,20));//可以直接打印返回值
        System.out.println(x);//也可以打印x的值,与上面对比
        System.out.println("main end");
    }
}

class MethodExer {

    public static void test(int a) {
        System.out.println("test : " + a);
    }
    //这个方法功能弱,但是不用传参,调用简单,可以满足需求低只打印20*8矩形的客户
    public static void method1() {
        /*
        for (int i = 0;i < 20;i++) {
        System.out.println("********");
        }
        */

        method1(20,8);//也可以直接用重载方法进行调用
    }

    //这个方法功能强大,但是调用复杂,满足需求高的客户,所以各有各的好处,都应该保留
    public static int method1(int n,int m) {
        test(n);//会调用test方法,执行完,回到这里继续执行下面的语句
        for (int i = 0;i < n;i++) {
            for (int j = 0;j < m;j++) {
                System.out.print("*");
            }
            System.out.println();
        }
        short c = (short)(2 * (n + m));//对于返回值来说,short一样是整型,不会与int型的返回值冲突,显然有一定的兼容性,注意long型不行
        System.out.println(c);

        return c;
    }
    /*
    public static int method1(int n) {
        for (int i = 0;i < n;i++) {
                for (int j = 0;j < n;j++) {
                System.out.print("*");
            }
            System.out.println();
        }
        short c = (short)(4 * n);//对于返回值来说,short一样是整型,不会与int型的返回值冲突,显然有一定的兼容性,注意long型不行
        System.out.println(c);

        return c;
    }
    */
    public static int method1(int n) {
        int c = method1(n,n);//可以与上一个method1方法协同,比如上一个method1方法中,*换成了#,这里可以跟着自动变换
        
        return c;
    }


    public static void main(String[] args) {

        short a = 20;
        int b = 10;
        //long b = 10;//如果是long型,就会与赋值操作一样,大范围类型赋给小范围类型,会出错
        //method();
        method1(a,b);//本质上实参对形参是赋值操作,调用重载方法method1(两参数)
        method1(a);//调用重载方法method1(一参数)
        method1();//最简单的只打印20*8矩形的重载方法(无参数)
    }
}

2.6.2方法的重载

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可,与参数名无关。

重载与返回值类型无关,只和参数列表有关,且参数列表(参数个数或类型)必须不同。调用时就根据参数列表的不同来区别。

//下面是print方法的源码,之所以这个方法可以接收各种数据类型,就是因为方法的同名重载
//重载的好处就是对于功能类似的方法来说,只需要记一个名字就行,方便很多
public void print(boolean b) {
    write(b ? "true" : "false");
}
public void print(char c) {
    write(String.valueOf(c));
}
public void print(int i) {
    write(String.valueOf(i));
}
public void print(long l) {
    write(String.valueOf(l));
}
public void print(float f) {
    write(String.valueOf(f));
}
public void print(double d) {
    write(String.valueOf(d));
}
public void print(char s[]) {
    write(s);
}
public void print(String s) {
    if (s == null) {
        s = "null";
    }
    write(s);
}
public void print(Object obj) {
    write(String.valueOf(obj));
}
//下面是重载的例子
public class OverLoadTest {

    public static int add(int a,int b) {//1
        System.out.println("add(int a,int b)……");
        int c = a + b;
        return c;
    }

    public static double add(double a,double b) {//2
        System.out.println("add(double a,double b)……");
        double c = a + b;
        return c;
    }

    public static double add(int a,double b) {//3
        System.out.println("add(int a,double b)……");
        double c = a + b;
        return c;
    }

    public static double add(double a,int b) {//4
        System.out.println("add(double a,int b)……");
        double c = a + b;
        return c;
    }

    public static void main(String[] args) {

        System.out.println(add(1,2));//如果方法1不存在,那么就没有完全匹配的方法了,只能进行兼容匹配,显然方法234都可以匹配,但是34的兼容度一样,注意不能出现匹配度相同的2个方法,否则会报错
        System.out.println(add(1.1,2.2));
        System.out.println(add(2,2.2));//若有方法3,则执行方法3,优先这个完全匹配,若没有方法3,则执行方法2,因为2.2只能适用于double,这是兼容匹配
    }
}

2.6.3方法的参数传递分析

Java里方法的参数传递方式只有一种:值传递。即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。

Java三部曲之JavaSE基础_第3张图片

上面的解读:栈:调用者在下,被调用者在上

方法调用是栈的结构,就是从上往下堆,像弹匣一样,最上面的子弹先打出,栈也一样,最上面的方法先弹栈,比如对于main方法来说,它是入口方法,所以它必然是待在栈底的,main方法调用了add方法,所以add方法紧接着堆在了main方法上方,add方法又调用了test方法,所以test方法又堆在了add方法上方,接着是参数的传递,对于main方法来说,它拥有三个变量,并按顺序结构执行语句,赋值后有short a = 20,byte b = 50,int c = ?(待传入),它将a与b的值传给了add方法的两个形参a和b,add方法呢,拥有三个变量int a = 20(main方法中的a传入的),int b = 50(main方法中的b传入的),short c = ?(还未执行),它获取实参后,按顺序执行,先执行test(a)方法,将a = 20的值传给test方法中的a,test方法执行完毕,该方法即进行弹栈,继续回到调用处,继续进行执行,经过short c = (short)(a + b)的运算,a,b两个int型数据运算后强转成short赋给c,恰好接得住,返回类型为int,也接得住short型,所以可以成功返回值,注意,此时add方法执行完毕,也要进行弹栈,同时返回值进入了临时变量int tmp的空间中暂存,临时变量的数据类型是由返回值类型确定的,并在下面调用处立即传给调用方法本身,并赋值给int c,显然int型接得住int型,接着打印c的值,main方法也执行完毕并弹栈,此时栈空了,即宣告进程结束。

class SimpleTest {
    //递归调用要创建栈帧,运算效率比较低,但是设计的复杂度也低
    //求n的阶乘,即求n*n-1的阶乘,用递归可以解决,因为该问题可以分解为一个小问题和另一个子问题
    public static int test(int n) {
        //return test(n);//递归调用,无限递归,栈溢出,变成死归
        if (n == 1) {
            return 1;//进入条件后,直接return 1;结束方法
        }
        return n * test(n - 1);//有限的递归,会一直压栈,压到n=1
    }

    public static void main(String[] args) {
        System.out.println(test(5));//调用test方法,传入实参5,进入test方法开始压栈,n!=5,返回4*test(4),test(4)的值不明,继续压栈,test(4),n!=4,返回4*test(3)
        //以此类推,压栈到test(1),n==1,返回1,直接代入上面的调用处,2*test(1)处,得到2*1=2,返回给test(2)的调用处,3*test(2)处,得到3*2=6,返回给test(3)的调用处,4*test(3),
        //得到4*6=24,返回给test(4)的调用处,5*test(4),得到5*24=120,返回给main方法中的test(5)的调用处,打印出来120,实际上就是5的阶乘,注意所有的返回值都是存储在临时空间中的,并且只在弹栈的时刻存储下来
    }
}

双重递归的调用

class DiGui {
    //双重递归调用的次数
    public void binomial() {recursion(5);}

    private static int count = 0;

    public static int recursion(int k) {
        count++;
        System.out.println("count1:" + count + " k:" + k);
        if (k <= 0) {
            return 0;
        }
        return recursion(k - 1) + recursion(k - 2);
    }
}

分析:

Java三部曲之JavaSE基础_第4张图片

  • 跨类调用

类.方法(实参列表);//跨类调用格式

//在Another类中
public class Another {

    public static int add(int a,int b) {
        return a + b;
    }
}
//从Another类中调用add方法
class Method {
    
    public static void main(String[] args) {
        System.out.println("main begin");

        System.out.println(Another.add(10,20));//跨类调用,到Another类中调用add方法

        System.out.println("main end");
    }
}

2.7项目1《家庭记账软件》

  • 模拟实现一个基于文本界面的《家庭记账软件》
  • 掌握初步的编程技巧和调试技巧
  • 主要涉及以下知识点:

局部变量和基本数据类型

循环语句

分支语句

方法调用和返回值的接收

简单的屏幕输出格式控制

ps:IPV4指的是4个字节(32bit)表示一个IP地址,可以有40多亿个IP,已经不够用了;IPV6指16个字节(128bit)表示一个IP地址,有非常庞大的数量级的IP地址。

ps2:C语言会把地址当整数来用,容易出问题,Java区分开地址与整数,安全性高一些。

  • 需求说明

记录家庭的收入、支出,并能够打印收支明细表

采用分级菜单方式。主菜单如下:

---------------家庭收支记账软件---------------

                      1 收支明细

                      2 登记收入

                      3 登记支出

                      4 退 出

                      请选择(1-4):_

假设家庭起始基本金为10000元;

每次登记收入(菜单2)后,收入的金额应累计到基本金上,并记录本次本次收入明细,以便以后的查询;

每次登记支出(菜单3)后,支出的金额应从基本金上扣除,并记录本次本次支出明细,以便以后的查询;

查询收支明细(菜单1)时,将显示所有的收入、支出明细列表。

操作如下:

---------------家庭收支记账软件-------------

                1 收支明细

                2 登记收入

                3 登记支出

                4 退 出

                请选择(1-4): 2

本次收入金额:1000

本次收入说明:劳务费

---------------家庭收支记账软件---------------

                1 收支明细

                2 登记收入

                3 登记支出

                4 退 出

                请选择(1-4): 1

---------------当前收支明细记录---------------

收支  账户金额  收支金额  说 明

收入  11000       1000        劳务费

支出  10200       800          物业费

------------------------------------------------------

---------------家庭收支记账软件-------------

                1 收支明细

                2 登记收入

                3 登记支出

                4 退 出

                请选择(1-4): 4

确认是否退出(Y/N):_

提示:明细表格的对齐,可以简单使用制表符'\t'来实现

  • 基本金和收支明细的记录

基本金的记录可以用int类型的局部变量来实现:int balance = 10000;

收支明细记录可以用String类型的变量来实现,其初始值为明细表的表头。例如:

String details = "收支\t账户金额\t收支金额\t说 明\n";

在登记收支时,将收支金额与balance相加或相减,收支记录直接串接到details后面即可。

  • 流程图

Java三部曲之JavaSE基础_第5张图片

  • 键盘访问的实现

项目中提供了Utility.java类,可用来方便地实现键盘访问。

import java.util.*;

public class Utility {
    private static Scanner scanner = new Scanner(System.in);
    
    public static char readMenuSelection() {
            char c;
            for (; ; ) {
                String str = readKeyBoard(1);
                c = str.charAt(0);
                if (c != '1' && c != '2' && c != '3' && c != '4') {
                    System.out.print("选择错误,请重新输入:");
                } else break;
            }
            return c;
        }

        public static int readNumber() {
            int n;
            for (; ; ) {
                String str = readKeyBoard(4);
                try {
                    n = Integer.parseInt(str);
                    break;
                } catch (NumberFormatException e) {
                    System.out.print("数字输入错误,请重新输入:");
                }
            }
            return n;
        }

        public static String readString() {
            String str = readKeyBoard(8);
            return str;
        }

        public static char readConfirmSelection() {
            char c;
            for (; ; ) {
                String str = readKeyBoard(1).toUpperCase();
                c = str.charAt(0);
                if (c == 'Y' || c == 'N') {
                    break;
                } else {
                    System.out.print("选择错误,请重新输入:");
                }
            }
            return c;
        }

        private static String readKeyBoard(int limit) {
            String line = "";

            while (scanner.hasNext()) {
                line = scanner.nextLine();
                if (line.length() < 1 || line.length() > limit) {
                    System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:");
                    continue;
                }
                break;
            }

            return line;
        }
}

该类提供了以下静态方法:

-public static char readMenuSelectio():该方法读取键盘,如果用户键入'1'-'4'中的任意字符,则方法返回。返回值为用户键入字符。

-public static int readNumber():该方法从键盘读取一个不超过4位长度的整数,并将其作为方法的返回值。

-public static String readString():该方法从键盘读取一个不超过8位长度的字符串,并将其作为方法的返回值。

-public static char readConfirmSelection():该方法从键盘读取'Y'或'N',并将其作为方法的返回值。

//家庭记账软件源代码
public class FamilyAccount {

    public static void main(String[] args) {
        //声明保存家庭基本金变量,初值是10000
        int balance = 10000;
        //声明一个字符串类型的变量,就是记账本,初值是表头
        String details = "---------------当前收支明细记录---------------\n收支\t账户金额\t收支金额\t说  明\n";
        //声明布尔变量用来控制循环
        boolean LoopFlag= true;
        //写一个循环,显然循环次数是未知的,选择do while循环
        do {
            //打印主菜单
            System.out.print("---------------家庭收支记账软件---------------\n\n");
            System.out.println("                1 收支明细");
            System.out.println("                2 登记收入");
            System.out.println("                3 登记支出");
            System.out.print("                4 退   出\n\n");
            System.out.print("                请选择(1-4):");
            //通过调用工具方法获取用户从键盘输入的真实的选择
            char choice = Utility.readMenuSelection();//这个方法返回一个char型值,用一个变量接收
            //对用户的输入做分支
            switch (choice) {
            //如果是'1',打印记账本
            case '1':
                System.out.print(details);
                break;
            //如果是'2',处理收入
            case '2':
                //打印提醒信息“本次收入金额:”
                System.out.print("本次收入金额:");
                //从键盘读取金额
                int income = Utility.readNumber();//这个方法返回一个整数:金额
                //打印提醒信息“本次收入说明:”
                System.out.print("本次收入说明:");
                //从键盘读取说明
                String incomedescription = Utility.readString();
                //调整余额
                balance += income; 
                //串接本次详细字符串,串接到details后面
                details = details + "收入\t" + balance + "\t\t" + income + "\t\t" + incomedescription + "\n";//汉字后面跟1个制表符"\t",数字后面要跟2个制表符"\t"才能对齐
                break;
            //如果是'3',处理支出
            case '3':
                //打印提醒信息“本次支出金额:”
                System.out.print("本次支出金额:");
                //从键盘读取金额
                int outcome = Utility.readNumber();//这个方法返回一个整数:金额
                //打印提醒信息“本次支出说明:”
                System.out.print("本次支出说明:");
                //从键盘读取说明
                String outcomedescription = Utility.readString();
                //调整余额
                balance -= outcome; 
                //串接本次详细字符串,串接到details后面
                details = details + "收入\t" + balance + "\t\t" + outcome + "\t\t" + outcomedescription + "\n";
                break;
            //如果是'4',设置控制循环的布尔为false
            case '4':
                //打印提醒信息“确认是否退出(Y/N):”
                System.out.print("确认是否退出(Y/N):");
                //从键盘读取选择'Y'与'N'
                char confirm = Utility.readConfirmSelection();
                //选择'Y'时退出程序
                if (confirm == 'Y') {
                LoopFlag = false;
                }
                break;
            default:
                System.out.println("非法的输入");
                break;
            }
        } while(LoopFlag);
    }
}

第三章:面向对象编程

3.1面向对象与面向过程

二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为。面向对象,将功能封装进对象,强调具备了功能的对象。

面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。

Java三部曲之JavaSE基础_第6张图片

3.1.1面向对象思想:

人作为一个对象,可以打开“冰箱”这个对象,并且是通过调用冰箱自己的“打开”方法来打开冰箱的,当然人也可以打开“洗衣机”这个对象等;可以看到冰箱这个对象中,封装了“开门”,“关门”这两种方法;优势就是可以重复使用各个对象,提高代码的复用性。

  • 程序员从执行者转化成了指挥者。
  • 完成需求时:

先去找具有所需功能的对象来用;

如果该对象不存在,那么创建一个具有所需功能的对象;

这样简化开发并提高复用。

  • 类(class)和对象(object)是面向对象的核心概念。

类是对现实世界事物的描述,是抽象的、概念上的定义;//比如"学生"这个抽象的概念,就是类

对象是实际存在的该类事物的一个个体,因而也称实例。//"小明"这个具体的学生,就是对象

  • “万事万物皆对象”

Java三部曲之JavaSE基础_第7张图片

  • 可以理解为:类 = 汽车设计图;对象 = 实实在在的汽车;//显然先有类后有对象
  • 面向对象程序设计的重点是类的设计;
  • 定义类其实是定义类中的成员(成员变量和成员方法)。

现实世界万事万物是由分子、原子构成的。同理,Java代码世界是由诸多个不同功能的类构成的;

现实世界中的分子、原子又是由什么构成的呢?原子核、电子!那么,Java中用类class来描述事物也是如此;

属性(变量):对应类中的成员变量(描述事物的特征);

行为:对应类中的成员方法(描述事物的行为)。

  • 设计类模板:
/*
类:对事物的描述,是抽象的
对象:这类事物的具体的个体,也叫实例

*/
public class Teacher {//这个Teacher是类类型,是一个复合类型,由各种基本类型组合而成的,比如这里就是由String,int,String组合而成的

    //变量(属性),描述事物的特征,数据部分
    //成员变量,就是在方法外的变量,专门用于描述事物的特征
    String name;//成员变量又叫对象属性,实例变量(与类变量区分)
    int age;
    String gender;

    //成员方法,描述事物的行为,动作,功能
    //修饰符 返回值类型 方法名(参数列表) {方法体}
    public void lesson() {
        System.out.println("老师在上课");
    }

    public void eat(String some) {
        System.out.println("老师在吃" + some);
    }

    //对象的详细信息,返回的详细信息是一个字符串
    public String say() {

        String str = "姓名" + name + ",年龄" + age + ",性别" + gender;//成员变量可以在各个方法中直接使用,但static方法访问非static,编译不通过。
        return str;
    }
}
  • 根据类模板new出对象:
public class TeacherTest {

    public static void main(String[] args) {
        //类已经就绪了
        //根据类模板创建对象
        Teacher t = new Teacher();//new可以根据Teacher类,创建一个新对象,并赋值给Teacher型变量t,t就是一个对象了
        //new后面的叫构造器,new出的对象都在“堆”里面,不在“栈”里,“栈”里面都是局部变量
        t.name = "佟刚";//给对象的变量赋值
        t.age = 40;
        t.gender = "男";

        System.out.println(t.name);//打印出对象t的变量值或叫属性值
        System.out.println(t.age);
        System.out.println(t.gender);

        t.lesson();//调用对象t的lesson方法
        t.eat("苹果");//调用对象t的eat方法,并传String型的实参进去
        String s = t.say();//不用传参数,返回值传给字符串型变量s
        System.out.println(s);//打印出字符串s
    }
}

引用在栈内存中,对象在堆内存中,引用变量是不是也就是一种局部变量。

Java三部曲之JavaSE基础_第8张图片

  • 引用变量与交换引用:
class TeacherTest2 {

    public static void main(String[] args) {

        Teacher t1 = new Teacher();//t1究竟是什么?它是引用变量,与基本变量不同,它保存的是对象的地址,实际上永远获取不了对象本身,引用变量是通过对象的地址去访问对象的
        t1.name = "佟刚";
        t1.age = 40;
        t1.gender = "男";

        String str = t1.say();
        System.out.println(str);

        Teacher t2 =new Teacher();
        t2.name = "程程";
        t2.age = 18;
        t2.gender = "女";

        System.out.println(t2.say());

        //交换引用
        Teacher tmp = t1;//创建一个临时的引用变量tmp,让它接收t1保存的对象地址
        t1 = t2;//此时把t1保存的对象地址刷成t2的也没关系,因为t1原来指向的对象有tmp在指向,所以不会变成垃圾对象
        t2 = tmp;//再把t2保存的对象地址刷成tmp的,也就是t1原来保存的对象地址,此时t2指向的对象就是t1原来指向的对象了

        System.out.println(t2.age);

        t1 = t2;//t1的地址刷成了t2的,t1就指向t2指向的对象了,t1原来指向的对象没有了指向它的引用变量之后,会被识别为垃圾对象,GC会对其进行回收
        //若t1 = null;一样会使t1指向的对象被清理回收
        t1.age = 10;//此时通过t1修改age之后,t2指向的对象的age也会一起改变

        System.out.println(t2.age);//此时t2.age也被修改为10了
    }
}

注:看有几个对象实体产生,就看new了几次;GC是对每个对象都引用的,当没有别的引用变量指向该对象时,GC对其进行回收,回收就是,把该对象占用的内存标记为可用。但对象变垃圾也不一定立刻被清理,取决于GC什么时候工作。

  • 显式初始化与隐式初始化
//设计类
public class Teacher {

    String name;//变量必须初始化才能用,但基本变量必须显式初始化,即int x = 10;如果int x;再打印x,会报错,而JVM会对实例变量进行隐式初始化,比如String name;相当于String name = null;它自带初值null
    int age;//int age;相当于int age = 0;它自带初值0
    String gender = "男";//显然实例变量也可以进行显示初始化


    public void lesson() {
        System.out.println("老师在上课");
    }

    public void eat(String some) {
        System.out.println("老师在吃" + some);
    }

    //对象的详细信息,返回的详细信息是一个字符串
    public String say() {

        String str = "姓名" + name + ",年龄" + age + ",性别" + gender;
        return str;
    }
}
//new出对象
class TeacherTest2 {

    public static void main(String[] args) {

        Teacher t1 = new Teacher();//不给对象的属性赋值,但对象属性(实例变量)会自带隐式初始化,打印也不会出错
        System.out.println(t1.say());//打印出实例变量的隐式初始化的值

        Teacher t2 = new Teacher();
        System.out.println(t2.say());//虽然打印结果与t1一样,但是他们对应的是两个不同的对象,只是里面的值恰好一样而已,注意,对象是独立的实体
    }
}

public class TeacherTest {

    public static void main(String[] args) {

        Teacher t1 = new Teacher();
        t1.name = "佟刚";
        t1.age = 40;
        t1.gender = "男";

        String str = t1.say();
        System.out.println(str);

        Teacher t2 =new Teacher();
        t2.name = "程程";
        t2.age = 18;
        t2.gender = "女";//即使在类中已经对实例变量进行了显式初始化,但是这里依然可以按需求修改该实例变量的值

        System.out.println(t2.say());
    }
}

Java三部曲之JavaSE基础_第9张图片

注意:局部变量是隶属于方法的,它是在栈中的,会快速地经过压栈弹栈快速产生与销毁,所以没有隐式初始化;而成员变量是定义在方法外的,感觉有一定的慎重性,new出来的对象也就自带隐式初始化;除了基本数据类型之外的变量类型都是引用类型,如上面的Teacher及前面讲

  • 私有变量与this关键字
public class Teacher {
	//封装:成员私有化,目的是保护成员,通过方法间接处理成员可以在方法中做验证
	private String name;//private表示私有的,成员一旦被私有化,这个成员只能在本类中使用了
	private int age;
	private String gender = "男";//变量是可以有修饰符修饰的,但是有的可以缺省,比如public
	
	public void setName(String name) {//通过这个方法可以使对象间接设置私有属性name,有参数无返回
		//name = name;//局部变量name与成员变量name重名,那么这个name是谁呢,实际上有一个就近原则,哪个离得近就是哪个,这里局部变量name更近,所以左边的name就是局部变量name
		this.name = name;//this指当前对象,this.name就是当前对象的属性,也就是本类中的name了,此时实参传的值就可以成功了
		//谁是当前对象呢,实际上,谁调用方法,谁就是this,比如执行的是t1.setName(),那么后续的this都是t1指向的对象
 	}

	public String getName() {//通过这个方法可以使对象间接读取私有属性name,无参数有返回
		return this.name;//这个就近原则来看,离此处最近的就是实例变量name,其他方法中的同名变量不在就近原则的考虑范围
	}
	
	public void setAge(int age) {//为了看起来清晰,最好使用完整单词
		if (age < 0 || age > 120){//间接赋值有其保护作用,对实参传来的值进行有效性验证
			System.out.println("年龄不合法,请重新输入");
			return;//结束方法,即弹栈,不给age赋a的值的机会
		}
		this.age = age;
	}
	
	public int getAge() {
		return this.age;
	}
	
	public void setGender(String gender) {
		if (gender == "男" || gender == "女") {//有条件赋值,合法的情况下才对其赋值
			this.gender = gender;
		}
	}
	
	public String getGender() {
		return this.gender;
	}
	
	public String say() {
		
		String str = "姓名" + name + ",年龄" + age + ",性别" + gender;
		return str;
	}
}
//私有变量怎么间接访问
public class TeacherTest {
	
	public static void main(String[] args) {
		
		
		Teacher t2 =new Teacher();
		/*
		t2.name = "程程";//一旦属性被私有化了,就不能直接访问了
		t2.age = 18;
		t2.gender = "女";
		*/
		t2.setName("程程");//可以通过特定的方法访问私有变量
		t2.setAge(100);
		t2.setGender("女");
		
		System.out.println(t2.say());//通过成员方法可以间接访问
	}
}
  • 格式

1.定义类(考虑修饰符、类名);

2.编写类的属性(考虑修饰符、属性类型、属性名、初始化值);//修饰符和初值可以缺省

3.编写类的方法(考虑修饰符、返回值类型、方法名、形参等)。

Java三部曲之JavaSE基础_第10张图片

 类可以嵌套,有内部类,但是方法永远不能嵌套。

  • 修饰符

Java三部曲之JavaSE基础_第11张图片

  • 匿名对象

我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。

如:new Person().shout();

如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象;

我们经常将匿名对象作为实参传递给一个方法调用。

  • 构造器(构造方法):是JVM创建对象时主动调用的一个特殊方法,作用是在创建对象时作初始化工作

构造器的特征:

它具有与类相同的名称,是唯一允许首字母大写的方法

它不声明返回值类型。(与声明为void不同)

不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值

只能在创建对象时调用一次

构造器的作用:创建对象时给对象进行初始化,注意构造器只是创建对象时的一步,创建对象的过程是虚拟机完成的很复杂

如:Order o = new Order();

       Person p = new Person(“Peter”,15);

如同我们规定每个“人”一出生就必须先剪脐带 ,  我们就可以在“人”的构造方法中加入完成“剪脐带”的程序代码,于是每个“人”一出生就会自动完成“剪脐带”,程序就不必再在每个人刚出生时一个一个地告诉他们要“剪脐带”了。

如果在类中没有提供构造器,编译器会自动添加一个缺省构造器。

缺省构造器特点:修饰符与类一致,无参,没有代码

如果类中提供了构造器,编译器就不会添加缺省构造器了

输入javap 类名,就可以查看所有的方法,包括缺省的构造器

 Java三部曲之JavaSE基础_第12张图片

 构造器也支持重载:

public class Person {
	
	private String name = "小刚";
	private int age = 33;
	//通常重载构造器只需要保留2个就行,一个无参的,一个有参的,无参的是固定输出,有参的是可变输出
	public Person() {//构造器的重载,无形参的构造器,调用简单,但对象的属性不能一步到位,却很重要,JavaBean规范要求有它,子类会默认用它,便于反射
		//构造器没有返回值类型
		this("小明",75);//在构造器中使用this,完成对其他重载构造方法的间接调用
		//注意,this(...)即调用其他构造方法的语句必须放在第一行,否则会报错
		System.out.println("Person()...")
		//this.name = "小明";
		//this.age =75;
		
	}
	
	public Person(String name,int age) {//构造器,有形参,对象属性一步到位,调用复杂,但是没有无参构造器重要
		//this();//所有的子类构造器中,至少要有一个不调用其他构造器,否则会变成无限递归
   	
		System.out.println("Person(String name,int age)...")
		
		this.name = name;
		this.age = age;
	}
	
	public void setAge(int age) {
		if (age > 0 && age < 130) {
			this.age = age;
		}
	}
	
	public int getAge() {
		return this.age;
	}
	
	public String say() {
		return "姓名:" + name + ",年龄:" + age;
	}
}
//构造方法的调用,初始化
public class TestPerson {
	
	public static void main(String[] args) {
		Person b1 = new Person();
		System.out.println(b1.say());
		//System.out.println(b.getAge());
		
		//b.setAge(75);
		//Person b2 = new Person("小军",19);
		//System.out.println(b2.say());
		
	}
}

执行顺序:

先调用的先压栈:Person();

后调用的后压栈:Person(String name,int age);

栈上面的方法先弹栈:Person(String name,int age);先弹栈

栈下面的方法后弹栈:Person();后弹栈

Java三部曲之JavaSE基础_第13张图片

  • 创建对象流程图

Java三部曲之JavaSE基础_第14张图片

Java三部曲之JavaSE基础_第15张图片

属性定义在方法区(永久区)的类模板中,那么方法在哪里呢,实际上方法是以方法代码的形式也保存在永久区内,等待被调用,此时方法是静态的,被调用后,才变成活动的。

地址0x1234究竟是什么,它通常被保存为16进制形式,其实0x1234就是首字节的地址,它可以配合各属性的存储字节数,来推算哪几个字节处保存的是哪个属性值。

  • 对象传递
//定义类
public class Teacher {
	
	private String name;
	private int age;
	private String gender = "男";
	
	public Teacher() {
		
		this.name = "程程";
		this.age = 20;
		this.gender = "女";
	}
	
	public Teacher(String name,int age,String gender) {
		
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
	
	public void setAge(int age) {
		if (age < 0 || age > 120){
			System.out.println("年龄不合法,请重新输入");
			return;
		}
		this.age = age;
	}
	
	public int getAge() {
		return this.age;
	}
	
	public void lesson(Computer computer) {//传入Computer类型的变量
		
	System.out.println(name + "老师在用电脑[" + computer.say() + "]上课");
	}
	
	public String say() {
		
		String str = "姓名" + name + ",年龄" + age + ",性别" + gender;
		return str;
	}
}
//Computer类
public class Computer {
	
	private double cpu;
	private int memory;
	
	public Computer() {
		
	}
	
	public Computer(double cpu,int memory) {
		
		this.cpu = cpu;
		this.memory = memory;
	}
	
	public void setCpu(double cpu) {
		
		this.cpu = cpu;
	}
	
	public double getCpu() {
		
		return cpu;
	}
	
	public void setMemory(int memory) {
		
		this.memory = memory;
	}
	
	public int getMemory() {
		
		return memory;
	}
	
	public String say() {
		
		return "cpu:" + cpu + "GHz,内存:" + memory + "G";
	}
}
//测试类调用
class TeacherTest1 {
	//对象的传递,Java中的传递只有值传递,这里传递的值就是对象地址的值
	public static void test1(int n) {//int n,int是数据类型,n是int型的变量
		
		n++;//值变了,但是基本变量的值改变影响不到别的类中的基本变量,它们实际上就不是一个变量
		System.out.println(n);
	}	
	
	public static void test2(Teacher tt) {//Teacher tt,Teacher是类类型,tt是Teacher型的引用变量
		
		System.out.println("test2..." + tt.say());
		tt.setAge(10);//但是这里,引用了对象的方法,让它将对象的地址值修改了,凡是调用该对象的,都会得到修改后的值
		System.out.println("test2..." + tt.say());//这里会变
	}		
	
	
	public static void main(String[] args) {
		
		Teacher t =new Teacher("程程",20,"女");
		System.out.println(t.say());
		
		int n = 10;
		test1(n);//基本变量的传值调用
		System.out.println(n);//但是影响不了这里的n的值,因为值不会变到这里来
		
		test2(t);//传入一个Teacher型变量,那边就可以调用t的方法和属性了
		//用setAge修改后,这里也会变,这就是间接修改
	}
}


public class TeacherTest {
	
	public static void main(String[] args) {
		
		Teacher t1 =new Teacher();
		Computer com = new Computer(3.8,16);
		
		t1.lesson(com);
	}
}
  • 练习
//Boy类
public class Boy {
	
	private String name;
	private int age;
	
	public Boy() {
		this.name = "小军";
		this.age = 22;
	}
	
	public Boy(String name,int age) {
		
		this.name = name;
		this.age = age;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

	
	public void marry(Girl girl) {
		
		System.out.println(name + "娶到了" + girl.getName());
		girl.marry(this);//直接调用girl的marry方法,把当前对象this,将boy对象作为参数传入即可
	}
	
	public void shout(Girl girl) {
		System.out.println(girl.getName() + ",你愿意嫁给我吗");
		girl.shout(this);
	}
}
//Girl类
public class Girl {
	
	private String name;
	
	public Girl() {
		this.name = "小红";
	}
	
	public Girl(String name) {
		this.name = name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public void shout(Boy boy) {
		System.out.println(boy.getName() + ",我愿意");	
	}
	
	public void marry(Boy boy) {
		
		System.out.println(this.name + "嫁给了" + boy.getName());
	}
}
//测试类
public class BoyAndGirlTest {
	
	public static void main(String[] args) {
		
		Boy b = new Boy("小刚",25);
		Girl g = new Girl("丽丽");
		
		b.shout(g);
		b.marry(g);

	}
}
  • 对象关联
//在本类中把另一个类的对象作为我的属性,就是关联了另一个类的对象
//属性是成员,成员之间可以互访,就可以解决大量调用其他对象的问题
public class Teacher {
	
	private String name;
	private int age;
	private String gender = "男";
	private Computer myComputer;//关联了的对象
  
	public Teacher() {
		
		this.name = "程程";
		this.age = 20;
		this.gender = "女";
	}
	
	public Teacher(String name,int age,String gender) {
		
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
	
	public void setAge(int age) {
		if (age < 0 || age > 120){
			System.out.println("年龄不合法,请重新输入");
			return;
		}
		this.age = age;
	}
	
	public int getAge() {
		return this.age;
	}
	
	public void lesson() {//可以直接用自己关联的myComputer对象啦
		
		System.out.println(name + "老师在用电脑[" + myComputer.say() + "]上课");
	}
	
	public String say() {
		
		String str = "姓名" + name + ",年龄" + age + ",性别" + gender + "我的电脑:" + myComputer.say();
		return str;
	}
}
  • JavaBean

JavaBean是一种Java语言写成的可重用组件。

所谓javaBean,是指符合如下标准的Java类:

类是公共的;

有一个无参的公共的构造器;

有属性,且有对应的get、set方法。

用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

意思就是说:所有的对象可以变成成员变量,然后每个方法return出来的值,就可以是某个对象,该对象可以包含多个多种类型的值和方法。

  • UML图

Java三部曲之JavaSE基础_第16张图片

  • package

package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包(若缺省该语句,则指定为无名包)。

它的作用:告诉编译器,当前源文件中的所有类生成.class文件后,所有的.class文件都要保存到的包目录结构。

它的意义:如果是同一个包的类,可以直接使用(注意同一个包是指所有子包都是同一个包的)。

使用package后的限制:

1)使用package以后,编译必须使用-d,javac -d 目标目录(通常使用.目录) 源文件名;

如 javac -d . Person.java Phone.java PersonTest.java,.目录就是当前目录,就是目标目录,也就是要建此包的目录位置

也可以直接索引到指定目录:javac -d D:\MyWork\develop Phone.java

2)使用package后,在别的包中使用本类时,必须使用全限定类名:包名.子包名.子子包名.子子子包名.类名

//com.atguigu.javase.javabean包目录下
package com.atguigu.javase.javabean;

public class Phone {
	
	private String os;
	private double screen;
	
	public Phone() {}
	
	public Phone(String os,double screen) {
		this.os = os;
		this.screen = screen;
	}
	
	public String say() {
		return "操作系统:" + os + ",屏幕:" + screen + "英寸";
	}
}

package com.atguigu.javase.javabean;

public class Person {
	
	private String name = "小刚";
	private int age = 33;
	private Phone myPhone;
	
	public Person() {}
	
	public Person(String name,int age,Phone myPhone) {
		this.name = name;
		this.age = age;
		this.myPhone = myPhone;
	}
	
	public void setAge(int age) {
		if (age > 0 && age < 130) {
			this.age = age;
		}
	}
	
	public int getAge() {
		return this.age;
	}
	
	public String say() {
		return "姓名:" + name + ",年龄:" + age + ",手机:" + myPhone.say();
	}
	
	public void call() {
		
		System.out.println(name + "用手机[" + myPhone.say() + "]打电话");
	}
}
//com.atguigu.javase.test包目录下的类中,去调用com.atguigu.javase.javabean包目录下的类,要使用全限定类名
package com.atguigu.javase.test;

public class PersonTest {
	
	public static void main(String[] args) {
		
		com.atguigu.javase.javabean.Person b = new com.atguigu.javase.javabean.Person("程程",20,new com.atguigu.javase.javabean.Phone("Windows",32.52));//跨包使用类,要使用全限定类名
		System.out.println(b.say());
		b.call();
	}
}
//最终各自的.class文件,各自保存到各自的包目录下

最后在命令行窗口也要使用全限定名,java com.atguigu.javase.test.PersonTest,成功运行。并且在哪里javac,就要在哪里java。

它的格式为:

package 顶层包名.子包名.子子包名.子子子包名;//可以无限开辟子包,类似于目录

package 机构类型.机构名称.项目名称.模块名称;

举例:pack\Test.java
package p1;    //指定类Test属于包p1
public class Test{
    public void display(){
        System.out.println(in  method display());
    }
}

包对应于文件系统的目录,package语句中,用“.”来指明包(目录)的层次;

包通常用小写单词,类名首字母通常大写。

Java三部曲之JavaSE基础_第17张图片

包帮助管理大型软件系统:将语义近似的类组织到包中;解决类命名冲突的问题。

包可以包含类和子包。

  • import

import的作用:导入其他包的类,类一旦导入,在本文件中的所有类中都可以直接使用类的简单名称。也就是告诉编译器,使用该类时,到哪个包下面去定位类。

package com.atguigu.javase.test;
import com.atguigu.javase.javabean.Person;
import com.atguigu.javase.javabean.Phone;
 //import com.atguigu.javase.javabean.*;//可以用这个,但是可读性不好

public class PersonTest {
	
	public static void main(String[] args) {//不用使用全限定名了
		
		Person b = new Person("程程",20,new Phone("Windows",32.52));//跨包使用类,要使用全限定类名
		System.out.println(b.say());//java.lang.System,这其实也是一个包,但是它是默认导入的
		b.call();
	}
}
  • JDK中主要的包介绍

1.java.lang----包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。

2.java.net----包含执行与网络相关的操作的类和接口。

3.java.io----包含能提供多种输入/输出功能的类。

4.java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。

5.java.text----包含了一些java格式化相关的类。

6.java.sql----包含了java进行JDBC数据库编程的相关类/接口。

7.java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。

8.java.applet----包含applet运行所需的一些类。

3.2数组

3.2.1定义

  • 数组是多个相同类型数据的组合,实现对这些数据的统一管理
  • 数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型
  • 数组属引用类型,数组型数据是对象(object),数组中的每个元素相当于该对象的成员变量
package com.atguigu.javase.test;

/**
	数组的元素就相当于数组的属性
	数组元素的访问:首地址+偏移量,数组名[下标],偏移量是根据数组元素的数据类型确定的
	编译器不会做下标检查,也就是int[] arr = new int[4];的数组,给arr[4]赋值,编译器javac也能通过,但是java时会报错
**/
class MyDate {
	
	private int year;
	private int month;
	private int day;
	
	public MyDate() {}
	public MyDate(int year,int month,int day) {
		this.year = year;
		this.month = month;
		this.day = day;
	}
	
	public void setYear(int year) {
		this.year = year;
	}
	
	public int getYear() {
		return year;
	}
	
	public void setMonth(int month) {
		this.month = month;
	}
	
	public int getMonth() {
		return month;
	}
	
	public void setDay(int day) {
		this.day = day;
	}
	
	public int getDay() {
		return day;
	}
	
	public String say() {
		return year + "年" + month + "月" + day + "日";
	}
} 

class ArrayTest4 {
	
	public static void main(String[] args) {
		//引用类型的数组静态初始化1
		MyDate[] arr = new MyDate[]{
			new MyDate(1998,1,9),
			new MyDate(2022,3,8)
		};
		
		//遍历
		for (int i = 0;i < arr.length;i++) {
			System.out.println(arr[i].say());
		}
	}
}

public class ArrayTest {
	
	public static void main(String[] args) {
		
		int[] arr;//arr是一个引用型变量
		arr = new int[5];
		arr[0] = 3;
		arr[2] = 1;
		arr[arr[2]] = arr[0];
		arr[4] = arr[1] + arr[3];
		
		System.out.println(arr.length);//访问数组的长度
		//arr.length = 6;//会出错,length不能修改,它是被final修饰的常量,不允许修改
		//arr = new short[4];//不能修改元素的数据类型
		
		System.out.println(arr[0]);
		System.out.println(arr[1]);
		System.out.println(arr[2]);
		System.out.println(arr[3]);
		System.out.println(arr[4]);
		//元素为对象的数组
		MyDate[] arr1 = new MyDate[3];//三个元素
		
		arr1[0] = new MyDate(1998,1,9);//只赋值2个
		arr1[1] = new MyDate(2022,3,8);
		//arr1[2]没有赋值,这个对象元素为空,称为空洞,容易出错
		/*
		for (int i = 0;i < 3;i++) {
			System.out.println(arr1[i].say());//会出现空指针异常,表示有.操作
		}
		*/
		for (int i = 0;i < 3;i++) {
			if (arr1[i] != null) {
				System.out.println(arr1[i].say());
			} else{
				System.out.println(arr1[i]);//没有对象,直接输出null
			}
		}		
	}
}
class ArrayTest2 {
	
	public static void main(String[] args) {//赋值
		
		char[] chArr = new char[26];
		for (int i = 0;i < chArr.length;i++) {//把循环因子当下标来用
			chArr[i] = (char)('a' + i);
		}
		//遍历请单独使用一个循环,与赋值区分开
		for (int i = 0;i < chArr.length;i++) {//遍历
			int j = i + 1;
			System.out.println("chArr[" + j + "] : " + chArr[i]);
		}
	}
}
class ArrayExer1 {
	
	public static void main(String[] args) {
		
		char[] chArr = new char[36];
		for (int i = 0;i < 26;i++) {
			chArr[i] = (char)('A' + i);
		}
		
		for (int i = 26;i < 36;i++) {
			chArr[i] = (char)('0' + i -26);
		}		
		
		//遍历请单独使用一个循环,与赋值区分开
		for (int i = 0;i < chArr.length;i++) {
			int j = i + 1;
			System.out.println("chArr[" + j + "] : " + chArr[i]);
		}
	}
}

2.2一维数组初始化

  • 静态初始化

基本类型元素:

int a[] = new int[]{ 3, 9, 8};

int[] a = {3,9,8};

引用类型元素:

MyDate dates[] = {

        new MyDate(22, 7, 1964),

        new MyDate(1, 1, 2000),

        new MyDate(22, 12, 1964)

}

class ArrayTest3 {
	
	public static void main(String[] args) {
		//静态初始化1,灵活性强,功能更强大
		int[] arr1 = new int[]{2,3,5,6,7,9};//[]不能加长度
		//遍历
		for (int i = 0;i < arr1.length;i++) {
			System.out.println(arr1[i]);
		}
		//静态初始化2
		int[] arr2 = {2,3,5,6,7,9};
		for (int i = 0;i < arr2.length;i++) {
			System.out.println(arr2[i]);
		}
		//arr2 = {1,2,3};//修改就会报错
		arr2 = new int[]{1,3,5};//这样可以对数组进行修改
	}
}
  • eclipse

保存文件可以自动编译

测试类必须是公共类

ctrl:停在关键字上,可以查看源码,ctrl+f可以快速查找关键字

重命名:refactor->rename

光标停留在变量处,ctr+1->弹出快速修正,比如全限定类的选择

shift+回车:直接换新行

alt+/:快捷帮助;如main+alt/;sysout+alt/

ctr+alt+上下方向:复制当前行代码

alt+上下方向:移动代码

alt+s:set/get方法,构造器,重写equals或hashCode方法

左边双击产生小圆圈断点:程序在debug时会在断点处停下

在debug模式下:

F5:进入代码执行细节

F6:一行一行地执行代码

F7:当前的方法无论还有多少,直接执行返回

F8:直接执行下一个断点或结束

  • 数组的操作

包括数组的反转,扩容,缩减,增强for循环,取子数组,统计运算等

package com.atguigu.javase.test;

import java.awt.datatransfer.SystemFlavorMap;

public class ArrayTest {

	public static void main(String[] args) {
		//取子数组
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));
		}

		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
		
		int[] newArr = new int[8];//声明新数组,长度与老数组一致
		int count = 0;//声明计数器,初值是0
		for (int i = 0;i < arr.length;i++) {//遍历老数组,找有效元素
			if (arr[i] % 2 != 0) {//判断是否为有效元素
				newArr[count] = arr[i];//此时恰好count为0,也就是新数组的第一个位置成功被赋值,后面以此类推,按顺序插入所有奇数
				count++;//自增计数
			}
		}
		
		int[] finalArr = new int[count];//创建长度为count的最终数组,也就是恰好可以排除掉newArr1中0的数组
		for (int i = 0;i < count;i++) {//把有效非0元素按顺序插入最终数组
			finalArr[i] = newArr[i];
		}
		
		newArr = finalArr;//刷掉newArr1
		arr = newArr;//刷掉arr
		
		for (int tmp : arr) {//遍历
			System.out.print(tmp + " ");
		}
		System.out.println();
	}		
	
	public static void main9(String[] args) {
		//数组的扩容
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));//数组中的数有正有负了
		}

		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
		
		int[] newArr = new int[(int)(arr.length * 1.5)];//创建新数组
		for (int i = 0;i < arr.length;i++) {//以短数组的长度为准
			newArr[i] = arr[i];//依次复制元素
		}
		arr = newArr;//老引用指向新数组,释放老数组
		for (int tmp : newArr) {
			System.out.print(tmp + " ");//会多出几个默认赋值的0来
		}
		System.out.println();
	}	
	
	public static void main8(String[] args) {
		//数组的缩减
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));//数组中的数有正有负了
		}

		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
		
		int[] newArr = new int[arr.length / 2];//创建新数组
		for (int i = 0;i < newArr.length;i++) {//依次把老数组中的值复制到新数组中
			newArr[i] = arr[i];
		}
		arr = newArr;//老引用指向新数组,释放老数组
		for (int tmp : newArr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
	}	
		
	
	public static void main7(String[] args) {
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));//数组中的数有正有负了
		}

		for (int i = 0;i < arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		
		int maxIndex = 0;//找最大值所在的下标,初值为0
		for (int i = 0;i < arr.length;i++) {
			if (arr[i] > arr[maxIndex]) {//通过值比大小
				maxIndex = i;//刷新成较大的那个值的下标
			}
		}
		System.out.println("最大值:" + arr[maxIndex]);//输出最大值下标对应的值
	}	
		
	//找出能被7整除的数中的最大值和最小值
	public static void main6(String[] args) {
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));//数组中的数有正有负了
		}

		for (int i = 0;i < arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		
		int max = 0x80000000;//用最小值赋值,肯定比数组中的最小值要小,可以被刷新
		int min = 0x7FFFFFFF;//用最大值赋值,肯定比数组中的最大值要大,可以被刷新
		for (int i = 0;i < arr.length;i++) {
			if (arr[i] % 7 == 0) {//有条件计算
				if (max < arr[i]) {//如果遍历到的值比max还大
					max = arr[i];//就把max刷成该值
				}
				if (min > arr[i]) {//如果遍历到的值比min还小
					min = arr[i];//就把min刷成该值
				}
			}
		}
		if (max == 0x80000000) {//如果最大值没被刷过,说明没有被7整除的数
			System.out.println("没有能被7整除的数");
		} else {
			System.out.println("最大值:" + max);
			System.out.println("最小值:" + min);
		}
	}	
	
	//求数组中的最大值最小值
	public static void main5(String[] args) {
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)(Math.random() * 200);
		}

		for (int i = 0;i < arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		
		int max = arr[0];//初值的选取很重要,不能选一个比所有数都大的数作为初值
		int min = arr[0];
		for (int i = 0;i < arr.length;i++) {
			if (max < arr[i]) {//如果遍历到的值比max还大
				max = arr[i];//就把max刷成该值
			}
			if (min > arr[i]) {//如果遍历到的值比min还小
				min = arr[i];//就把min刷成该值
			}
		}
		System.out.println("最大值:" + max);
		System.out.println("最小值:" + min);
	}	
	
	//求能被7整除的数的均值
	public static void main4(String[] args) {
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)(Math.random() * 200);
		}

		for (int i = 0;i < arr.length;i++) {
			if (arr[i] % 7 == 0) {
				System.out.print(arr[i] + " ");
			}
		}
		System.out.println();
		
		int sum = 0;
		int count = 0;
		for (int i = 0;i < arr.length;i++) {
			if (arr[i] % 7 == 0) {//有条件累加与计数
				sum += arr[i];//求和
				count++;//计数
			}
		}
		if (count != 0) {
			int avg = sum / count;
			System.out.println("求和:" + sum);
			System.out.println("计数:" + count);
			System.out.println("均值:" + avg);
		} else {
			System.out.println("没有能被7整除的数");
		}
	}
	
	public static void main3(String[] args) {
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = 2 + i + 1;
		}
		//遍历
		for (int i = 0;i < arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		
		int sum = 0;
		for (int i = 0;i < arr.length;i++) {
			sum += arr[i];
		}
		int avg = sum / arr.length;
		System.out.println("求和:" + sum);
		System.out.println("均值:" + avg);
	}	
	
	public static void main2(String[] args) {
		//产生一个随机的浮点数
		double rand1 = Math.random();
		System.out.println(rand1);
		//产生0~100的随机整数
		int rand2 = (int)(Math.random() * 100);
		System.out.println(rand2);
		//产生一个20~50之间的随机整数
		int rand3 = (int)((Math.random() * 30) + 20);
		System.out.println(rand3);
	}
	
	public static void main1(String[] args) {
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = 2 + i + 1;
		}
		//遍历1
		for (int i = 0;i < arr.length;i++) {//经典for循环
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		//遍历2
		for (int i = 0;i < arr.length;i++) {//临时变量处理,不改变数组元素本身,保证安全性
			int tmp = arr[i];
			tmp *= 10;
			System.out.print(tmp + " ");
		}
		System.out.println();
		//遍历3,增强型for循环,或叫foreach
		/*
		for (元素数据类型 临时变量 : 数组名) {
			//处理临时变量
		}
		*/
		for (int tmp : arr) {//增强for很安全,因为它是只读访问
			tmp *= 10;
			System.out.print(tmp + " ");
		}
		System.out.println();
		//下标法不能用增强foreach了,只能用经典for
		//用下标法找能被7整除的最大值
		int maxIndex7 = -1;
		for (int i = 0;i < arr.length;i++) {
			if (arr[i] % 7 == 0) {
				if (maxIndex7 == -1) {//第一次找到能被7整除的数,无条件刷成i
					maxIndex7 = i;
				} else if (arr[maxIndex7] < arr[i]) {//接下来就可以正常进行判断了
					maxIndex7 = i;
				}
			}
		}
		if (maxIndex7 == -1) {
			System.out.println("没有能被7整除的数");
		} else {
			System.out.println(arr[maxIndex7]);
		}
		//反转数组
		//0 1 2 3 4 5 6 7 相当于首尾对应位置交换,第i个位置与第7-i个位置交换
		for (int i = 0;i < arr.length / 2;i++) {//奇数偶数都是数组长度除以2次,因为奇数除以2只保留整数部分
			int tmp = arr[arr.length - 1 - i];//临时值保存
			arr[arr.length - 1 - i] = arr[i];//交换数值
			arr[i] = tmp;
		}
		for (int i = 0;i < arr.length;i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		
	}
}

3.3项目2:客户信息管理软件

•模拟实现一个基于文本界面的《客户信息管理软件》

•进一步掌握编程技巧和调试技巧,熟悉面向对象编程

•主要涉及以下知识点:

–类和对象(属性、方法及构造器)

–类的封装

–引用数组

–数组的插入、删除

–对象的聚集处理

–多对象协同工作

需求说明:

模拟实现基于文本界面的《客户信息管理软件》。

该软件能够实现对客户对象的插入、修改和删除(用数组实现),并能够打印客户明细表。

项目采用分级菜单方式。主菜单如下:crud

-----------------客户信息管理软件-----------------

                        1 添 加 客 户

                        2 修改 客 户

                        3 删除 客 户

                        4 客户 列 表

                        5 退           出

                        请选择(1-5):_

•每个客户的信息被保存在Customer对象中。

•以一个Customer类型的数组来记录当前所有的客户

•每次“添加客户”(菜单1)后,客户(Customer)对象被添加到数组中。

•每次“修改客户”(菜单2)后,客户对象的属性被重新赋为新值。

•每次“删除客户”(菜单3)后,客户(Customer)对象被从数组中清除。

•执行“客户列表”(菜单4)时,将列出数组中所有客户的信息

“添加客户”的界面及操作过程如下所示:

  ……

                   请选择(1-5):1

---------------------添加客户---------------------

姓名:张三

性别:男

年龄:30

电话:010-56253825

邮箱:[email protected]

---------------------添加完成---------------------

“修改客户”的界面及操作过程如下所示:

  ……

                   请选择(1-5):2

---------------------修改客户---------------------

请选择待修改客户编号(-1退出):1

姓名(张三):

性别(男):

年龄(30):

电话(010-56253825):

邮箱([email protected]):[email protected]

---------------------修改完成---------------------

“删除客户”的界面及操作过程如下所示:

  ……

                   请选择(1-5):3

---------------------删除客户---------------------

请选择待删除客户编号(-1退出):1

确认是否删除(Y/N):y

---------------------删除完成---------------------

“客户列表”的界面及操作过程如下所示:

  ……

   请选择(1-5):4

---------------------------客户列表---------------------------

编号  姓名      性别    年龄   电话            邮箱

 1    张三       男      30     010-56253825   [email protected]

 2    李四       女      23     010-56253825    [email protected]

 3     王芳       女      26     010-56253825   [email protected]

-------------------------客户列表完成-------------------------

设计模式:

Java三部曲之JavaSE基础_第18张图片

CMUtility为键盘交互模块

package com.atguigu.cms.view;

import java.util.*;

public class CMUtility {
    private static Scanner scanner = new Scanner(System.in);
    
	public static char readMenuSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false);
            c = str.charAt(0);
            if (c != '1' && c != '2' && 
                c != '3' && c != '4' && c != '5') {
                System.out.print("选择错误,请重新输入:");
            } else break;
        }
        return c;
    }

    public static char readChar() {
        String str = readKeyBoard(1, false);
        return str.charAt(0);
    }

    public static char readChar(char defaultValue) {
        String str = readKeyBoard(1, true);
        return (str.length() == 0) ? defaultValue : str.charAt(0);
    }

    public static int readInt() {
        int n;
        for (; ; ) {
            String str = readKeyBoard(2, false);
            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }

    public static int readInt(int defaultValue) {
        int n;
        for (; ; ) {
            String str = readKeyBoard(2, true);
            if (str.equals("")) {
                return defaultValue;
            }

            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("")? defaultValue : str;
    }

    public static char readConfirmSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false).toUpperCase();
            c = str.charAt(0);
            if (c == 'Y' || c == 'N') {
                break;
            } else {
                System.out.print("选择错误,请重新输入:");
            }
        }
        return c;
    }

    private static String readKeyBoard(int limit, boolean blankReturn) {
        String line = "";

        while (scanner.hasNextLine()) {
            line = scanner.nextLine();
            if (line.length() == 0) {
                if (blankReturn) return line;
                else continue;
            }

            if (line.length() < 1 || line.length() > limit) {
                System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:");
                continue;
            }
            break;
        }

        return line;
    }
}

CustomerView为主模块,负责菜单的显示和处理用户操作:UI模块,用户界面

package com.atguigu.cms.view;

import com.atguigu.cms.domain.Customer;
import com.atguigu.cms.service.CustomerList;

public class CustomerView {
	//创建最大包含10客户对象的CustomerList对象,供以下各成员方法使用。
	//关联核心的管理器
	private CustomerList customerList = new CustomerList(10);
	//是项目的真正入口
	public void enterMainMenu() {
		//不能轻易返回
		//声明布尔恒为true,控制循环不断进行下去
		boolean loopFlag = true;
		while (loopFlag) {
			//打印主菜单
		  	System.out.println("-----------------客户信息管理软件-----------------");
		  	System.out.println("				  1 添 加 客  户 ");
		  	System.out.println("				  2 修 改 客 户");
		  	System.out.println("				  3 删 除 客 户");
		  	System.out.println("				  4 客 户 列 表");
		  	System.out.println("				  5 退           出\n");
		  	System.out.println("				    请选择(1-5):");

			//通过工具类获取用户的键盘输入
		    char choice = CMUtility.readMenuSelection();
			//对用户的输入分支
		  	switch (choice) {
			  	//如果是'1'调用方法addNewCustomer()
			  	case '1':addNewCustomer();break;
				//如果是'2'调用方法modifyCustomer()
			  	case '2':modifyCustomer();break;
				//如果是'3'调用方法deleteCustomer()
			  	case '3':deleteCustomer();break;
				//如果是'4'调用方法listAllCustomers()
			  	case '4':listAllCustomers();break;
				//如果是'5'修改控制循环的布尔
			  	case '5':loopFlag = false;break;
		  	}
		}
	}
	private void addNewCustomer() {//添加新客户
		
		System.out.println("---------------------添加客户---------------------");
		
		System.out.println("姓名:");
		String name = CMUtility.readString(10);//获取有长度限制的字符串
		System.out.println("性别:");
		char gender = CMUtility.readChar();
		System.out.println("年龄:");
		int age = CMUtility.readInt();
		System.out.println("电话:");
		String phone = CMUtility.readString(15);
		System.out.println("邮箱:");
		String email = CMUtility.readString(30);
		
		Customer cust = new Customer(name,gender,age,phone,email);
		boolean b = customerList.addCustomer(cust);
		if (b) {
			System.out.println("---------------------添加完成---------------------");
		} else {
			System.out.println("---------------------添加失败---------------------");
		}
		listAllCustomers();
	}
	private void modifyCustomer() {//修改客户
		
		System.out.println("---------------------修改客户---------------------");
		System.out.println("请选择待修改客户编号(-1退出):");//提醒语句
		int index = CMUtility.readInt();//获取索引值
		if (index == -1) {//弹栈退出
			return;
		}
		Customer cust = customerList.getCustomer(index - 1);//获取指定索引处的对象
		if (cust == null) {//
			System.out.println("---------------------索引超出范围---------------------");
			return;
		}
		System.out.println("姓名(" + cust.getName() + "):");
		String name = CMUtility.readString(10,cust.getName());//重载方法,不输入数值直接回车,会返回默认值,将老数值作为默认值,可以进行选择性修改
		cust.setName(name);//修改对象的值
		
		System.out.println("性别(" + cust.getGender() + "):");
		char gender = CMUtility.readChar(cust.getGender());
		cust.setGender(gender);
		
		System.out.println("年龄(" + cust.getAge() + "):");
		int age = CMUtility.readInt(cust.getAge());
		cust.setAge(age);
		
		System.out.println("电话(" + cust.getPhone() + "):");
		String phone = CMUtility.readString(15,cust.getPhone());
		cust.setPhone(phone);
		
		System.out.println("邮箱(" + cust.getEmail() + "):");
		String email = CMUtility.readString(30,cust.getEmail());
		cust.setEmail(email);
		
		System.out.println("---------------------修改完成---------------------");
		listAllCustomers();
	}
	private void deleteCustomer() {//删除客户
		System.out.println("---------------------删除客户---------------------");
		System.out.println("请选择待删除客户编号(-1退出):");//提醒语句
		int index = CMUtility.readInt();//获取索引值
		if (index == -1) {//判断是否退出
			return;//退出就直接弹栈
		} 
		System.out.println("确认是否删除(Y/N):");//提醒是否确认删除
		char choice = CMUtility.readConfirmSelection();//获取判断值
		if (choice == 'Y') {//判断是否确认删除
			boolean b = customerList.deleteCustomer(index - 1);//删除并接收返回值
			if (b) {//判断返回值是否为true
				System.out.println("---------------------删除完成---------------------");
			} else {//不为true要打印原因
				System.out.println("---------------------索引超出范围---------------------");
			}
		}
		listAllCustomers();
}
	private void listAllCustomers() {//客户列表
		System.out.println("---------------------------客户列表---------------------------");
		System.out.println("编号\t姓名\t性别\t年龄\t电话\t\t邮箱");
		Customer[] allCustomers = customerList.getAllCustomers();//获取全数组,完美数组
		for (int i = 0;i < allCustomers.length;i++) {//遍历数组
			System.out.println((i+1) + "\t" + allCustomers[i].say() + "");//进行打印
		}
		System.out.println("-------------------------客户列表完成-------------------------");
	}
}

CustomerList为Customer对象的管理模块,内部用数组管理一组Customer对象,并提供相应的添加、删除和获取方法,供CustomerView调用:后台服务

package com.atguigu.cms.service;

import com.atguigu.cms.domain.Customer;

/**
	CustomerList为Customer对象的管理模块,内部用数组管理一组Customer对象
	本类封装以下信息:
	Customer[] customers:用来保存客户对象的数组
	int realCount= 0:记录已保存客户对象的数量
	该类至少提供以下方法:
	public CustomerList(int totalCount) 
	public boolean addCustomer(Customer customer) 
	public boolean deleteCustomer(int index)
	public Customer[] getAllCustomers() 
	public Customer getCustomer(int index) 
 */

public class CustomerList {

	private Customer[] customers;//用来保存客户对象的数组,是一个对象数组哦!
	private int realCount = 0;//记录已保存客户对象的数量
	
	public CustomerList(int totalCount) {//传入构造器数组长度需求
		this.customers = new Customer[totalCount];//调用者在调用本类的构造器时就可以new出对象(这个对象的一个成员变量是数组)
		//调用构造器的同时就可以根据实际需求确定new出的这个成员变量(数组)的长度
	}
	
	/**
	  *   用途:将参数customer添加组中最后一个客户对象记录之后
	      参数:customer指定要添加的客户对象
	      返回:添加成功返回true;false表示数组已满,无法添加
	 */
	public boolean addCustomer(Customer customer) {//添加对象的功能
		//把参数中的对象保存在数组中,下标由计数器控制
		//调整计数器
		//返回true
		if (realCount == customers.length) {//计数器就是当前线性表的长度,如果线性表长度大于等于数组长度,表示数组已满
			return false;//提前return false,弹栈,不再执行下面的程序
		} else {//若数组未满
			this.customers[realCount] = customer;//将对象填入数组中对应的位置
			realCount++;//计数器自增
		}
		return true;
	}
	
	/**
	  * 用途:返回数组中记录的所有客户对象;
	     返回: Customer[]数组中包含了当前所有客户对象,该数组长度与对象个数相同。
	     返回一个完美数组。
	 */
	
	public Customer[] getAllCustomers() {
		Customer[] allcustomers = new Customer[realCount];//创建新数组,长度就是线性表长度realCount
		for (int i = 0;i < realCount;i++) {//遍历数组
			allcustomers[i] = this.customers[i];//一一对应传入新数组,没有空洞
		}
		return allcustomers;//返回完美数组
	}
	
	/**
	 * 用途:返回参数index指定索引位置的客户对象记录
	   参数: index指定所要获取的客户对象在数组中的索引位置
	   返回:封装了客户信息的Customer对象
	 */
	public Customer getCustomer(int index) {
		
		if (index >= realCount || index < 0) {//下标超出线性表长度,直接返回null
			return null;
		}
		return this.customers[index];//否则返回数组中的对应索引的元素--对象
		
	}
	
	/**
	 * 用途:从数组中删除参数index指定索引位置的客户对象记录
	   参数: index指定所删除对象在数组中的索引位置
	   返回:删除成功返回true;false表示索引无效,无法删除
	 */
	
	public boolean deleteCustomer(int index) {
		//把要删除的下标位置置为空洞
		//从要删除的下标位置开始,依次把右侧相邻的元素复制到左边
		//把之前的最右边的元素置为空洞
		//调整计数器
		
		if (index >= realCount || index < 0) {//判断索引下标是否超出范围
			return false;//超出范围返回false,弹栈结束程序
		}
		customers[index] = null;//把索引处,置为空洞,所以此处也可缺省,它不影响完美数组的提取
		for (int i = index;i < realCount - 1;i++) {//从索引开始,遍历数组,但是最右侧元素不再进行赋值
			customers[i] = customers[i + 1];//其他情况下,就将右侧的元素赋值给左边
		}
		customers[realCount - 1] = null;//将最右边的元素置为空洞,但是由于计数器的锁定,所以此句也不影响完美数组的提取
		realCount--;//调整计数器
		
		return true;
	}
}

Customer为实体对象,用来封装客户信息:封装实体类

package com.atguigu.cms.domain;

	/**
	 Customer为实体类,用来封装客户信息
	该类封装客户的以下信息:
	String name : 客户姓名
	char gender : 性别
	int age : 年龄
	String phone : 电话号码
	String email : 电子邮箱
	提供各属性的get/set方法
	提供所需的构造器(可自行确定)
 	*/
public class Customer {
	
	private String name;//客户姓名
	private char gender;//性别
	private int age;//年龄
	private String phone;//电话
	private String email;//邮箱
	
	public Customer() {}
	
	public Customer(String name,char gender,int age,String phone,String email) {
		
		this.name = name;
		this.gender = gender;
		this.age = age;
		this.phone = phone;
		this.email = email;
	}
	
	public void setName(String name) {//setn + alt/快捷键生成set方法
		this.name = name;
	}
	
	public String getName() {//getn + alt/快捷键生成get方法
		return this.name;
	}
	
	public void setGender(char gender) {
		this.gender = gender;
	}
	
	public char getGender() {
		return this.gender;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public int getAge() {
		return this.age;
	}
	
	public void setPhone(String phone) {
		this.phone = phone;
	}
	
	public String getPhone() {
		return this.phone;
	}
	
	public void setEmail(String email) {
		this.email = email;
	}
	
	public String getEmail() {
		return this.email;
	}
	
	public String say() {
		return name + "\t" + gender + "\t" + age + "\t" + phone + "\t\t" + email + "\n";
	}
}

CustomerMain为主类,包含项目入口方法

package com.atguigu.cms.main;

import com.atguigu.cms.domain.Customer;
import com.atguigu.cms.service.CustomerList;
import com.atguigu.cms.view.CustomerView;

public class CustomerMain {
	
	public static void main(String[] args) {//这个是项目的入口,或者是入口方法
		CustomerView customerView = new CustomerView();
		customerView.enterMainMenu();//注意这个入口方法不能随便结束,结束的话程序就会崩溃
	}
 }

3.2.3数组排序

冒泡排序:让每个值不断与右边的值对比,先把最大值冒到最右侧,然后是次大值冒到倒数第二位,以此类推,排序完成

选择排序:在当前值及之后的值中找最小值,把该最小值交换到当前值位置,然后让当前值右移,继续寻找最小值并交换到当前值,直到排序完成

快速排序:找一个键值,一般是初始值,让键值不断去与后面的值比较,比键值小的值安排到键的右侧,并将所有比键值小的值按顺序安排下去,并将原位置的值与其交换,最后再让键值与比键值小的值的最后一位进行交换,此时,键值左侧都是比键值小的值,右侧都是比键值大的值,继续在键值左侧和右侧分别进行递归,递归的终止条件是出现键索引的结尾值与开始值相差小于等于1的情况,也就是keyIndex-begin=1/0,表示键值本身就是最小值,或者键值左边只有一个比它小的值了

Arrays.sort(arr);//这是java快排的一个函数

package com.atguigu.javase.test;

public class ArrayTest {
	
	public static void main(String[] args) {
		//快速排序
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));
		}

		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
		
		quick(arr,0,arr.length);
		
		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
	}
	
	public static void quick(int[] arr,int begin,int end) {//结束索引不包含
		//快速排序
		if (end - begin <= 1) {//当结尾值与起始值相差小于等于1时,结束递归
			return;
		}
		//分区,分3个部分,中间是键,左边比键小,右边比键大
		//定位键索引最关键
		int key = arr[begin];//总是取第一个元素为键值
		int keyIndex = begin;//键索引,用于动态保存比键值小的值
		for (int i = begin + 1;i < end;i++) {//从键值右边第一个数开始排序,直到末尾值
			if (arr[i] < key) {//目的是找出比key值小的数
				keyIndex++;//比key值小的数,按顺序排放在键值右侧
				int tmp = arr[keyIndex];//具体表现为,让当前键索引处的值与找到的比key小的值换位置
				arr[keyIndex] = arr[i];
				arr[i] = tmp;
			}
		}
		//让键值归位到keyIndex位置处
		arr[begin] = arr[keyIndex];
		arr[keyIndex] = key;//归位
		
		//左子列递归
		quick(arr,begin,keyIndex);
		//右子列递归
		quick(arr,keyIndex + 1,end);
	}
	
	public static void main3(String[] args) {
		//选择排序
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));
		}

		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
		//找包括当前位置在内的最小值,用当前位置与其交换
		//移动当前位置,直到倒数第二个位置处为止
		for (int i = 0;i < arr.length - 1;i++) {//负责移动
			//以i为基准位置
			int minIndex = i;//找最小值所在的下标,初值为i
			for (int j = i + 1;j < arr.length;j++) {//因为minIndex=i,所以不需要与本身比较,有j=i+1
				if (arr[j] < arr[minIndex]) {//通过值比大小
					minIndex = j;//刷新成较小的那个值的下标
				}
			}
			//循环结束后,minIndex中保存的就是最小值下标
			//交换基准位置和minIndex最小值位置的值
			int tmp = arr[i];
			arr[i] = arr[minIndex];
			arr[minIndex] = tmp;
		}
		
		System.out.println("*********************************");
		
		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
	}
	
	public static void main2(String[] args) {
		//冒泡排序
		int[] arr = new int[8];
		for (int i = 0;i < arr.length;i++) {
			arr[i] = (int)((Math.random() * -200) + (Math.random() * 200));
		}

		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
		for (int i = 0;i < arr.length - 1;i++) {//一次次地循环,把第二大的值冒到倒数第二位,以此类推
			for (int j = 0;j < arr.length - 1 - i;j++) {//控制交换次数,一直交换下去,把最大值冒到最右边
				if (arr[j] > arr[j + 1]) {//比较左右值的大小
					int tmp = arr[j + 1];//左小右大就左右交换
					arr[j + 1] = arr[j];
					arr[j] = tmp;
				}
			}
		}
		System.out.println("****************************************");
		for (int tmp : arr) {
			System.out.print(tmp + " ");
		}
		System.out.println();
	}
	

3.2.4二维数组

以数组为元素的数组

  • 动态初始化

int[][] arr = new int[3][2];

定义了名称为arr的二维数组,二维数组中有3个一维数组,每个一维数组中有2个元素,一维数组的名称分别为arr[0],arr[1],arr[2],给第一个一维数组1脚标位赋值为78写法是:arr[0][1] = 78;

int[][] arr = new int[3][];

二维数组中有3个一维数组,每个一维数组都是默认初始化值null,可以对这三个一维数组分别进行初始化,arr[0] = new int[3];arr[1] = new int[1];

arr[2] = new int[2];注意:int[][]arr = new int[][3];//非法语句

  • 静态初始化

int[][] arr = new int[][]{{3.8,2},{2,7},{9,0,1,6}};

注意:int[] x,y[];其中x是一维数组,y是二维数组

  • 操作数组的工具类:Arrays

java.util.Arrays类包含了用来操作数组(比如排序和搜索)的各种方法。Arrays拥有一组static方法。

equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。

fill():将值填入array中。 

sort():用来对array进行排序。 

binarySearch():在排好序的array中寻找元素。 

System.arraycopy():array的复制。

package com.atguigu.javase.test;

public class ArrayArrayTest {
	
	public static void main(String[] args) {
	
		//创建一个拥有10个子数组的二维数组,每个子数组的长度是随机的2~5,里面的数据是随机的100以内的整数
		int[][] arrarr = new int[10][];
		for (int i = 0;i < arrarr.length;i++) {
			arrarr[i] = new int[(int)(Math.random() * 4 + 2)];
			for (int j = 0;j < arrarr[i].length;j++) {
				arrarr[i][j] = (int)(Math.random() * 100);
			}
		}
		//找出最大值
		int max = -1;
		for (int i = 0;i < arrarr.length;i++) {
			for (int j = 0;j < arrarr[i].length;j++) {
				if (arrarr[i][j] > max) {
					max = arrarr[i][j];
				}
			}
		}
		//遍历二维数组
		for (int[] child : arrarr) {
			for (int tmp : child) {
				System.out.print(tmp + " ");
			}
			System.out.println();
		}
		System.out.println("最大值:" + max);
	}
	public static void main1(String[] args) {
		
		int[][] arrarr = new int[5][];//创建了具有5个子数组的二维数组,子数组都是null
		for (int i = 0;i < arrarr.length;i++) {//按一维数组的个数遍历
			arrarr[i] = new int[i + 2];//对每个一维数组进行初始化
			for (int j = 0;j 
  • main()方法理解

由于java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。

Java三部曲之JavaSE基础_第19张图片

3.2.5可变参数

1.可变参数:方法参数部分指定类型的参数个数是可变多个

2.声明方式:方法名(参数的类型名...参数名)

3.可变参数方法的使用与方法参数部分使用数组是一致的

4.方法的参数部分有可变形参,需要放在形参声明的最后

package com.atguigu.javase.test;

public class VarargsTest {
	
	/*
	public static int avg(int a,int b) {
		return (a + b) / 2;
	}
	
	public static int avg(int a,int b,int c) {
		return (a + b + c) / 3;
	}
	*/
	
	public static int max(int... values) {
		int max = 0x80000000;
		for (int i = 0;i < values.length;i++) {
			if (values[i] > max) {
				max = values[i];
			}
		}
		return max;
	}
	public static void main(String[] args) {
		System.out.println("max:" + max(1,2,3,4,5,-1,-10,100));
	}
	
	//方法的可变参数只能有一个,并且只能放末尾
	public static int avg(int... values) {//可变参数,个数任意,相当于传入一个数组values[n],n为元素个数
		int sum = 0;//兼容性强,可以接入数组对象和散元素
		for (int i = 0;i < values.length;i++) {
			sum += values[i];
		}
		return sum / values.length;
	}
	//public static int avg(int[] values)//这个与上面的不能重载
	public static void main1(String[] args) {//String[]也可以写成String...
		
		System.out.println(avg(5,9));//如果是散元素,编译器会做一个操作:avg(new int[]{5,9})
		System.out.println(avg(5,1,9));
		System.out.println(avg(5));
		//System.out.println(avg());//不能为空,avg(new int[]{})
		int[] arr = {1,2,3,4};
		System.out.println(avg(arr));//如果直接传入数组,不做任何处理
	}
}

第四章:高级类特性

4.1继承

public class Student extends Person {

      public String school;

}//Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以利用。

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

子类继承了父类,就继承了父类的方法和属性(不包括构造器),包括父类的私有方法,但是只有所有权,没有直接访问权。

在子类中,自动拥有父类中定义的方法和属性,也可以创建新的数据和方法。

在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。

子类不能直接访问父类中私有的(private)的成员变量和方法,一般要通过get/set方法间接访问。

Java三部曲之JavaSE基础_第20张图片

 一个子类可以有多个父类,但是离得最近的父类是直接父类,并且只有一个直接父类,也就是不支持多重继承,但是可以有间接父类,也就是多层继承。

//父类
package com.atguigu.javase.inheritence;

public class Person {
	
	private String name;
	private int age;
	private String gender;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}
	
	public void setAge(int age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String say() {
		return "姓名:" + name + "年龄:" + age + "性别" + gender;
	}
}
//子类
package com.atguigu.javase.inheritence;

public class Chinese extends Person {
	
	String shuxiang;
	
	public void spring() {
		System.out.println("中国人过年");
	}
}
//测试类
package com.atguigu.javase.inheritence;

public class PersonTest {
	public static void main(String[] args) {
		Chinese ch = new Chinese();
		ch.setName("张三");
		ch.setAge(20);;
		ch.setGender("男");;
		ch.shuxiang = "猪";
		
		System.out.println(ch.getName());
		System.out.println(ch.getAge());
		System.out.println(ch.getGender());
		System.out.println(ch.shuxiang);
		
		ch.spring();
		System.out.println(ch.say());
	}
}

4.2方法覆盖

在子类中可以根据需要对从父类中继承来的方法进行改造,也称方法的重写、重置。在程序执行时,子类的方法将覆盖父类的方法。

要求:

覆盖方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型。否则可能报错,也可能重载,就不能覆盖了。

事实上,返回值类型只要子类返回值类型小于等于父类就行。

覆盖方法不能使用比被重写方法更严格的访问权限。

覆盖和被覆盖的方法必须同时为非static、非private、非final的。

子类方法抛出的异常不能大于父类被重写方法的异常。

package com.atguigu.javase.inheritence;

public class Chinese extends Person {
	
	String shuxiang;
	String gender;//可以创建与父类相同的属性,并且调用子类的gender属性,优先就近调用子类的特有属性gender
	//访问super.gender才是父类的gender属性,super也不是父类对象的意思,它只是一个标识符
	//而this指的是子类对象整体,包括从父类继承来的和自己特有的
	public void spring() {
		System.out.println("我是" + getName() +  ",中国人过年");//子类中访问父类的私有方法,要用间接访问
	}
	
	//方法覆盖
	/*
	public String say() {
		return "姓名:" + getName() + ",年龄:" + getAge() + ",性别:" + getGender() + ",属相:" + shuxiang;
	}
	*/
	
	@Override//一种特殊的注释,可以被程序员和编译器,JVM识别
	//@Override就是告诉编译器,下面的方法要覆盖了,请提前做语法检查,方法覆盖必须加注解
	public String say() {//加了注解Override,如果方法没有覆盖就会提醒报错
		return super.say() + ",属相:" + shuxiang;//super,作用是调用从父类继承的成员
	}//另外对于直接父类和间接父类,甚至更上层的父类来说,super都能标识它们,但是对于它们各自特有的属性a,super.a遵循就近原则
	//指的是直接父类的a属性,但如果直接父类没有a属性,会继续向上寻着a属性,总之就近原则。
 	//如果直接父类和间接父类都有a属性呢,那super用完了也访问不到间接父类的a属性,这时候只能在间接父类中设置对应的getA1()方法间接访问它了
}

4.3构造器在子类中的调用

子类中所有的构造器默认都会访问父类中空参数的构造器。

当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器,且必须放在构造器的第一行。

如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错。

子类构造器中不管调用本类还是父类构造器,最终都将调用到父类构造器,这样看,第一步执行的就是父类构造器。

注意:因此在父类中提供无参构造器是非常重要且必需的。

另外,构造器不能继承,所以如果直接父类没有无参构造器(只定义了有参构造器),子类又没有显示调用直接父类的有参构造器,那么即便间接父类有无参构造器,也不能默认调用它,因为直接父类不能继承到间接父类的构造器。

另外一定是先父类构造器执行,再执行子类构造器!

Java三部曲之JavaSE基础_第21张图片

/*
public class Person {	
	private String name;
	private int age;
	private String gender;
	

	
	public Person(String name,int age,String gender) {//只定义了有参构造器时,就没有默认的无参构造器了
		this.name = name;
		this.age = age;
		this.gender = gender;
		System.out.println("Person(String name,int age,String gender)...");
	}
 */


package com.atguigu.javase.inheritence;

public class Chinese extends Person {
	
	String shuxiang;
	String gender;
	
	public Chinese() {//这就是默认构造器,无参无语句块
		//super();//其实缺省了这个语句,子类构造器默认直接调用父类的无参构造器,并且super()必须在第一行,和this()的调用很像
		//糟了,父类如果没有无参构造器了,子类只好显式地调用本类或者父类的其他构造器了
		super("张三",22,"男");//并且要将该构造器置于第一行,那如果父类的无参构造器又恢复了呢,没问题,依然调用显式处理的有参构造器
		System.out.println("Chinese()...");
	}
 	public Chinese(String name,int age,String gender,String shuxiang) {//子类的全参构造器
		super("张三",22,"男");//由于父类的私有属性不能直接赋值,采用父类的构造器传值,达到相同效果
		shuxiang = "猪";//子类的特有属性就可以直接赋值了
		System.out.println("Chinese(String name,int age,String gender,String shuxiang)...");
	}



}
  • 访问权限修饰符

Java三部曲之JavaSE基础_第22张图片

4.4多态

多态:子类对象的多种父类形态, 换言之就是把子类对象作为父类对象来使用。

ps:引用变量的多态性

一个基本型变量只能有一种确定的数据类型。

一个引用类型变量可能指向(引用)多种不同类型的对象。

子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。

本态:子类对象的本类形态

多态引用:子类对象赋值于父类类型的引用变量。相当于把子类对象中的父类成员赋值给父类类型的引用变量,也就是部分赋值。一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法(多态副作用)。

本态引用:子类对象赋值于本类类型的引用变量。

1.对象的多态性

Person p = new Chinese();//看右面,子类对象拥有多种父类形态(这些父类来源于单继承的多层父类),因为它是从上面的父类继承而来的,所以可以赋值给每一个父类类型的引用变量。

2.引用变量的多态性

Person p = new Chinese();//看左面,父类类型的引用具有多态性,它可以引用各个子类对象(包括下面的每一层子类对象和继承该父类的不同的子类对象,比如Person p = new American();)

Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。

Person p = new Chinese();//左边声明的是编译器识别的,是编译时类型,右边new的是实际运行时的概念,是运行时类型。

若编译时类型和运行时类型不一致,就出现多态。

  • 虚拟方法调用

编译时p为Person类型,而方法的调用是在运行时确定的,所以调用的是Chinese类的sayHello()方法

/*
//	Chinese子类方法重写
@Override public void sayHello() {编译通过后,运行时,执行的是子类对象的方法
		System.out.println("吃了吗?");
	}
 //American子类方法重写
@Override public void sayHello() {编译通过后,运行时,执行的是子类对象的方法
		System.out.println("How are you?");
//父类方法
public void sayHello() {//编译器会先检查父类中有没有sayHello()方法,有的话,才能接着调用子类中对应的方法
		System.out.println("Hello!");
	}
 */
public static void main(String[] args) {
		//多态
		Person p = new Chinese();//只是把子类对象Chinese的父类成员方法赋值给父类类型的引用变量
		
		p.sayHello();//此时对于子类对父类重写的方法来说,p调用的sayHello()方法是子类对象的重写方法而非父类方法
  		//左面是编译时类型,右面是运行时类型,编译要保证通过,但是最后执行的是运行时类型
    	p = new American();//实体改变了
		p.sayHello();//变成了调用American实体对象的sayHello()方法		
	}

最后说一句,如果子类没有重写父类的方法呢,答案是,那就直接执行父类的方法。

注意注意:成员变量不具有多态性,只看引用变量所属的类:

Person p = new Chinese();

p.name;//访问的是Person类的成员变量name,而非Chinese类的成员变量name,这一点应与成员方法进行比较。

4.4.1多态数组

	public static void main3(String[] args) {
		
		Person[] arr = new Person[5];
		arr[0] = new Chinese("张三",30,"男","牛");
		arr[1] = new American("Jack",35,"male",true);
		arr[2] = new Person("某人",20,"未知");
		arr[3] = new American("Rose",20,"female",false);
		arr[4] = new Beijing("李四",40,"女","猪");
		
		for (int i = 0;i < arr.length;i++) {
			System.out.println(arr[i].say());
		}
		
		System.out.println("*********************");
		//对多态数组中的元素按年龄选择排序
		for (int i = 0;i < arr.length - 1;i++) {
			int minIndex = i;
			for (int j = i + 1;j < arr.length;j++) {
				if (arr[j].getAge() < arr[minIndex].getAge()) {
					minIndex = j;
				}
			}
			Person tmp = arr[i];
			arr[i] = arr[minIndex];
			arr[minIndex] = tmp;
		}
		
		for (int i = 0;i < arr.length;i++) {
			System.out.println(arr[i].say());
		}
	}
	public static void main(String[] args) {
		//创建一个多态数组,可以容纳各种不同类型的对象时,需要多态,这就是多态数组
		Person[] arr = new Person[5];
		arr[0] = new Chinese("张三",30,"男","牛");
		arr[1] = new American("Jack",35,"male",true);
		arr[2] = new Person("某人",20,"未知");
		arr[3] = new American("Rose",20,"female",false);
		arr[4] = new Beijing("李四",40,"女","猪");
		
		for (int i = 0;i < arr.length;i++) {
			System.out.println(arr[i].say());
		}
		
		System.out.println("*********************");
		//对多态数组中的元素按年龄冒泡排序
		for (int i = 0;i < arr.length - 1;i++) {
			for (int j = 0;j < arr.length - 1 - i;j++) {
				if (arr[j].getAge() > arr[j + 1].getAge()) {
					Person tmp = arr[j + 1];
					arr[j + 1] = arr[j];
					arr[j] = tmp;
				}
			}
		}
		for (int i = 0;i < arr.length;i++) {
			System.out.println(arr[i].say());
		}
	}

4.4.2多态参数

从子类到父类的类型可以自动进行

从父类到子类的类型转换必须通过造型(强制类型转换)实现

无继承关系的引用类型间的转换是非法的

在造型前可以使用instanceof操作符测试一个对象的类型

Java三部曲之JavaSE基础_第23张图片

public class PersonTest {
	
	//方法可以接收任意Person及其子类对象
	public static void test(Person p) {//接收对象,参数是父类类型的,多态参数方法
		
		p.sayHello();
		
		//instanceof:判断左侧引用指向的对象的实体是否是右侧类型的一个对象,如果是返回true,不是就返回false
		if (p instanceof Beijing) {
			System.out.println("北京人");//最下层的子类放在最上面,以此类推
		} else if (p instanceof Chinese) {//造型有风险,先判断对象的类型,再进行改造
			Chinese ch = (Chinese)p;//看似强制类型转换,但实际上这只是类型还原,因为本来就是Chinese类型
			ch.spring();//转换成Chinese类型后,就克服了多态副作用,可以访问子类特有方法了
		} else if (p instanceof American) {
			//这也就是个对象造型的变化,其实并不是类型转换
			((American)p).christmas();//注意优先级.会先执行,所以应该把左边括起来
		} else if (p instanceof Person) {//最上层的父类放最下面
			System.out.println("普通人");
		}
	}
	
	public static void main(String[] args) {
		
		Chinese ch = new Chinese("张三",30,"男","牛");
		American am = new American("Jack",35,"male",true);
		Person p = new Person("某人",25,"未知");
		Beijing bj = new Beijing("李四",40,"女","猪");
		
		test(bj);//提高兼容性			
}	

4.5Object类

Object类是所有java类的根父类;

如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类。

1

public Object()

构造

构造方法

2

public boolean equals(Object obj)

普通

对象内容比较

this对象和obj对象

3

public int hashCode()

普通

获取对象的Hash码

4

public String toString()

普通

对象打印(或与字符串进行拼接)时自动调用

Hash码:散列码,会尽可能让对象散列开来,不要冲突,特征码,根据对象自身内容计算出的码

实际上,equals返回为true的两个对象,Hash码必然一致,Hash码就是由对象内容计算出现的,所以两对象equals为true,内容就一样,Hash码也就一样,equals与hashCode是一起出现的。同时也说明了,如果内容不一致,Hash码必须散列开。

alt+s,可以找到自动生成equals与hashCode方法的快捷方式。

  • Point类

package com.atguigu.javase.object;
/**
 *public boolean equals(Object obj) {
	return (this == obj);
  }
 *	判断当前对象的内容是否和参数中的对象相等,但是这个方法并不能做到真正的比较对象内容是否相等,需要根据实际需求重写
 * @author ZXF
 *
 */
public class Point {
	
	private int x;
	private int y;
	
	public Point() {

	}
	
	public Point(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
	
	public String say() {
		return "x:" + x + "y:" + y;
	}
	
	@Override
	public boolean equals(Object obj) {//这里就是多态参数,子类对象Point->父类参数Object,需要根据实际需求重写
		//在这里重写时,真正完成对象内容的比较
		//比较this对象和obj对象
		if (obj instanceof Point) {//只有传入的实参是Point子类,才进行内容比较,否则直接返回false
			Point p2 = (Point)obj;//父类引用变量obj接收的是子类Point对象,要想访问Point类的特有方法,需要造型转为Point子类
			if (this.x == p2.x && this.y == p2.y) {//这时就可以访问Point类的特有方法啦
				return true;
			}
		}
		return false;
	}
	
	//public native int hashCode();
	//hashCode方法是用C++写的底层方法,是与地址有关的,但返回值代表了内容的一致性
	@Override
	public int hashCode() {
		//特征码要和内容有关,但是这个哈希算法是人为设计的,并且永远不可能完美的,只需要让一个内容对应一个哈希码值就行了
		return Integer.parseInt(x + "" + y);//这个的意思就是让x与y转换成字符串拼接,再转成int型返回,就可以与内容一一对应了
		//但是这个hash码不好,比如103,0和10,30就会出问题。。。
	}
	
	@Override
	public String toString() {//写成一个可以描述对象详细信息的方法
		return this.say();
	}
}
  • 测试类
package com.atguigu.javase.object;

/**
 * 若obj1.equals(obj2)为真,则obj2.equals(obj1)也为真,这是交换性
 * 若又有obj1.equals(obj3)为真,则obj2.equals(obj3)也为真,这是传递性
 * @author ZXF
 *
 */

public class PointTest {
	
	public static void main(String[] args) {
		
		Point p1 = new Point(10,30);
		Point p2 = new Point(10,30);
		
		System.out.println(p1 == p2);//引用变量的==判断,比较的是地址值
		System.out.println(p1.equals(p2));//传入一个Point子类对象,判断p1,p2的内容是否一样
		
		System.out.println(p1.hashCode());//比较hash码值的异同
		System.out.println(p2.hashCode());
		/*这是默认的toString方法,根本看不清对象详细信息
		public String toString() {
			return getClass().getName() + "@" + Integer.toHexString(hashCode());
		}
		 */
		System.out.println(p1.toString());
		System.out.println(p2);//打印对象时,会自动调用toString方法,所以加不加toString都一样,实际上不要调用toString最好
		//因为如果p2是null,那么会出空指针异常,而如果不调用,最多输出个null
		String s = "abc" + p2;//与字符串进行拼接时,也会自动转换为toString
		System.out.println(s);
	}
}

4.6关键字static

  • static可以修饰属性、方法、代码块、内部类;

类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些类属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。

如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

  • 静态属性与静态方法是随着类模板创建的,存在永久区(方法区)里。
  • 被修饰后的成员具备以下特点:

可以隐式初始化,一般写0或null;

随着类的加载而加载;

优先于对象存在;

修饰的成员,被所有对象所共享;

访问权限允许时,可不创建对象,直接被类调用。

  • 隶属于对象的都是非静态,隶属于类的都是静态的。
package com.atguigu.javase.statictest;

public class Employee {
	
	public static String company = "atguigu";//所有对象共享的类属性(静态属性)
	
	public static void test() {//这就是静态方法 或 类方法,和对象无关
		System.out.println("公司:" + company);//静态成员之间可以互访
	}
	
	private static int no = 1;//定义静态变量no
	
	/*
	public static void test2() {
		this.name = "阿大";//静态环境中不能访问非静态成员,因为this与super都是非静态的
	}
	*/
	
	private static Employee e = new Employee();//放在外面就变成非静态成员了,相当于一个普通属性,所有需要让它变成静态
	//并且此时这就是类的一个静态成员了,保存着一个对象的地址,也就是在永久区里稳定地指向GC区的一个对象,这个对象就永远不会变垃圾
	public static void test2() {//静态环境中不能直接访问非静态成员
		//Employee e = new Employee();//需要先new出对象,再用对象去访问
		e.name = "阿大";
		e.age = 20;
		e.salary = 20000.0;
		
		System.out.println(e);
	}
	
	private int id;//希望id自动生成,这个变量是非静态的哦!!!
	private String name;
	private int age;
	private double salary;

	public Employee() {
		
	}
	
	public Employee(String name, int age, double salary) {//不需要传入id参数,它可以自动随着静态属性生成,无需传参
		test();非静态环境中可以访问静态成员,所以每次new对象时访问本构造器都会使静态成员执行一遍
		this.id = no++;//每new一个对象,其id都可以自动生成,也就是id自增一
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}
	
	@Override
	public String toString() {
		return "公司:" + company + ",工号:" + id + ",姓名:" + name + ",年龄:" + age + ",薪水:" + salary;
	}
}
//测试类
package com.atguigu.javase.statictest;

public class EmployeeTest {
	
	public static void main(String[] args) {
		
		Employee.test2();//可以直接调用类方法
		
		Employee ee = new Employee();
		ee.test2();//虽然通过对象调用类方法是可以的,但是在静态方法中仍然没有this,在静态方法中只能使用类成员
		//建议用静态环境去调用静态成员,必须用非静态环境去调用非静态成员
	}
	
	public static void main1(String[] args) {
		
		Employee.test();//对于类方法来说,不用new出对象,用类就能调用
		Employee.company = "尚硅谷";//对于类属性来说,不用new出对象,用类就能调用,并且还能修改属性值(是动态修改的哦)
		Employee.test();//修改属性值后,方法中获取的静态属性值,也随之变化
		
		Employee emp1 = new Employee("张三",30,20000);//传入非静态属性实参
		System.out.println(emp1);//toString所有的信息,包括静态与非静态
	}
}

4.7单例设计模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

package com.atguigu.javase.statictest;

/*
 * 单例:只允许有一个对象
 * 饿汉式单例:
 * 1)放在测试类中new操作,封装构造器
 * 2)在内部创建唯一的对象,并用私有的静态属性的引用指向唯一对象
 * 3)在类中再提供一个公共的静态方法,用以获取唯一对象
*/
class Singleton1 {
	private Singleton1() {}//封装构造器,类外部不能通过构造器new出对象,确保了单实例对象
	private static Singleton1 only = new Singleton1();//在本类中调用封装构造器new出一个对象,为了可以调用该对象并且始终是单一类对象
	//需要定义成private静态变量,能被类调用,但是不支持被修改
	public static Singleton1 getInstance() {//提供一个公共的静态方法,获取类对象,且类对象本身不会被修改
		return only;//用类直接调用静态方法getInstance()可以获取到这个唯一的对象
	}
}


public class SingletonTest {
	
	public static void main(String[] args) {
		//Singleton1 s1 = new Singleton1();
		//Singleton1 s2 = new Singleton1();
		//System.out.println(s1 == s2);
		/*
		Singleton1 s1 = Singleton1.only;//直接用类去调用静态变量,该静态变量就是new出的单例对象本身
		Singleton1.only = null;//这样修改对象的值也不被采纳
		Singleton1 s2 = Singleton1.only;
		System.out.println(s1 == s2);//注意这里是返回true,因为s1和s2指向的都是类对象,地址保存在永久区,是一样的~
		*/
		Singleton1 s1 = Singleton1.getInstance();		
		Singleton1 s2 = Singleton1.getInstance();		
		System.out.println(s1 == s2);
	}
}

4.8链表

package com.atguigu.javase.statictest;

class Node {
	
	Object value;
	Node next;
}

class Link {
	
	Node head;
	Node tail;
	int size = 0;
	
	public void add(Object val) {
		//把数据封到结点对象中
		Node newNode = new Node(); 
		newNode.value = val;
		
		if (head == null) {//头结点是null,没有指向任何对象
			head = newNode;//头结点刷成新结点,也就是头结点变量head指向新结点,原先的头结点被释放了
			tail = newNode;//尾结点刷成新结点,换种说法就是,给tail变量赋值newNode变量保存的对象的地址,让tail从null变成指向该对象
		} else {
			tail.next = newNode;//让尾结点变量的next属性保存newNode指向的对象的地址,也就是其next对应的新引用变量指向指向新结点
			tail = newNode;//尾结点这个引用变量本身也指向新结点
		}
		size++;
	}
	
	public void travel() {
		Node tmp = head;//定义临时变量为头结点
		
		while (tmp != null) {//判断当前结点是否为null,不为null就执行遍历,为null表示遍历到了最后一个结点
			System.out.println(tmp.value);
			tmp = tmp.next;
		}
	}
	
	public int size() {//返回链表长度
		return size;
	}
	
	public void remove(Object val) {
		if (head.value.equals(val)) {//判断val与头结点的数据域的值是否相等
			head = head.next;//相等就将头结点刷成下一个结点,头结点释放掉,也就删除了头结点
		} else {//如果要删的不是头结点
			Node prev = head;//头结点传给prevNode
			
			while (prev.next != null) {
				if (prev.next.value.equals(val)) {//如果头结点指向的结点的数据域的值等于val
					break;
				}
				prev = prev.next;//继续指向下一个结点
			}
			if (prev == tail) {//如果循环完也没找到要删除的结点
				return;//直接return,不做操作
			}
			prev.next = prev.next.next;//让prev指向的结点,变成prev指向的结点指向的结点,中间这个结点没引用变量指向,立刻变垃圾,删除成功
			if (prev.next == null) {//如果要删除的是尾结点
				tail = prev;//直接把尾结点刷成前一个,成功删除尾结点
			}
		}
		size--;
	}
}

public class FunnyTest {
	
	public static void main(String[] args) {
		Link link = new Link();
		
		link.add(10);
		link.add(2);
		link.add("dad");
		link.add(8);
		link.add(4);
		link.add(7);
		
		link.travel();
		
		System.out.println(link.size());
		System.out.println("**********************");
		link.remove(10);
		link.travel();
	}
}

4.9初始化块

  • 非静态代码块:没有static修饰的代码块

     1.可以有输出语句。

     2.可以对类的属性、类的声明进行初始化操作。

     3.可以调用静态的变量或方法。

     4.若有多个非静态的代码块,那么按照从上到下的顺序依次执行。

     5.每次创建对象的时候,都会执行一次。且先于构造器执行

  • 静态代码块:用static 修饰的代码块

    1.可以有输出语句。

    2.可以对类的属性、类的声明进行初始化操作。

    3.不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。

    4.若有多个静态的代码块,那么按照从上到下的顺序依次执行。

    5.静态代码块的执行要先于非静态代码块。

    6.静态代码块只执行一次。

//静态语句块,静态代码块
	static {
		System.out.println("Employee static {}....");//类加载时执行一次,就是类初始化器
		no = 100;//静态环境下可以访问静态成员
		//为什么private static int no = 1;还没有出现,就可以执行no = 100?因为private static int no = 1;分为两部分执行
		//private static int no;这一声明语句先执行,再按顺序执行赋值语句,所以步骤其实是
		//private static int no;
		//no = 100;
		//no = 1;
		//并且不能在此次打印no的值,必须初始化结束才能打印
	}
	
	{//非静态语句块,创建对象时执行,可以称为对象初始化器
	 //语句块先于构造器执行,且执行哪个构造器都要执行它,从顺序看,最后的赋值也应以构造器为准,和显式赋值相比,就看顺序去执行了
		System.out.println("Employee {}...");
	}

关于静态与非静态,构造器,显式初始化以及类加载之间的顺序的探究

package com.atguigu.javase.statictest;

//底层代码会把 非静态语句块,显式赋值,和构造器 合体,形成一个特殊方法
//会把所有static{}和静态属性的显式赋值 合体,形成一个特殊方法
class Base {
	{
		System.out.println("Base {}...");//1
	}
	static {
		System.out.println("Base static {}...");//2
	}
	public Base() {
		System.out.println("Base Base() {}...");//3
	}
}

class Sub extends Base {
	{
		System.out.println("Sub {}...");//4
	}
	static {
		System.out.println("Sub static {}...");//5
	}
	public Sub() {
		System.out.println("Sub Sub() {}...");//6
	}
}

public class OrderTest {
	
	public static void main(String[] args) {
		new Sub();//先检查类模板,所以就是加载类模板,首先就是加载类,然后由父到子创建对象
		//加载类(2父类静态语句块->5子类静态语句块)->创建父类对象(1父类非静态语句块->3父类构造器)->创建父类对象(4子类非静态语句块->6子类构造器)
	}
}

4.10关键字:final

在Java中声明类、属性和方法时,可使用关键字final来修饰,表示“最终”。//就是可读但不可被修改!

  • final标记的类不能被继承。提高安全性,提高程序的可读性。

String类、System类、StringBuffer类(一定重写了toString,equals,hashCode等方法,且很好用)

  • final标记的方法不能被子类重写。

Object类中的getClass()。

  • final标记的变量(成员变量或局部变量)即称为常量。名称大写,必须被赋值且只能被赋值一次。

final标记的成员变量必须在声明的同时或在每个构造方法中或代码块中显式赋值,然后才能使用。

final double PI = 3.14;public static final double PI = 3.14;//全局常量

//与方法有关的变量->局部变量:栈里保存,随方法压栈而产生,随方法弹栈而消失;

//与对象有关的变量->非静态成员变量:在GC堆中,随着对象的产生与消亡而产生与消亡

//与类有关的变量->静态成员变量:在堆的永久区中,随着类的加载而产生,一般不会消失

package com.atguigu.javase.statictest;

public class Employee {
	
	//静态语句块,静态代码块
	static {
		System.out.println("Employee static {}....");//类加载时执行一次,就是类初始化器
	
		COMPANY = "atguigu";//全局变量不能加this,静态空final就用静态语句块赋值
	}
	
	{//非静态语句块,创建对象时执行,可以称为对象初始化器
	 //语句块先于构造器执行,且执行哪个构造器都要执行它,从顺序看,最后的赋值也应以构造器为准,和显式赋值相比,就看顺序去执行了
		System.out.println("Employee {}...");
		this.id = no++;//咦,直接在非静态语句块中对空final量进行赋值就行啦,因为创建对象必须加载这个语句块,并且先于构造器执行
		//在这里对空final量进行赋值后,还需要把所有构造器中的对空fianl量赋值的操作全都终止,防止出现冲突
	}
	
	public static final String COMPANY;//所有对象共享的类属性(静态属性)
	

	
	private static int no = 1;//定义静态变量no

	
	private static Employee e = new Employee();//放在外面就变成非静态成员了,相当于一个普通属性,所有需要让它变成静态
	//并且此时这就是类的一个静态成员了,保存着一个对象的地址,也就是在永久区里稳定地指向GC区的一个对象,这个对象就永远不会变垃圾
	public static void test2() {//静态环境中不能直接访问非静态成员
		//Employee e = new Employee();//需要先new出对象,再用对象去访问
		e.name = "阿大";
		e.age = 20;
		e.salary = 20000.0;		
		System.out.println(e);
	}
	
	//空final很危险,在等待一次赋值,且不能改变
	private final int id;//希望id自动生成,这个变量是隶属于对象的非静态的final量
	private String name;
	private int age;
	private double salary;

	public Employee() {//直接调用无语句块的构造器创建对象,无法给final量进行一次赋值,显示报错
		//this.id = no++;//尽快在所有的构造器中都实现一次赋值即可
	}
	
	public Employee(String name, int age, double salary) {//不需要传入id参数,它可以自动随着静态属性生成,无需传参
		test();非静态环境中可以访问静态成员,所以每次new对象时访问本构造器都会使静态成员执行一遍
		//this.id = no++;//每new一个对象,其id都可以自动生成,也就是id自增一
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	public int getId() {//final量不能有set方法
		return id;
	}
	
	
	@Override
	public String toString() {
		return "公司:" + COMPANY + ",工号:" + id + ",姓名:" + name + ",年龄:" + age + ",薪水:" + salary;
	}
}

final对象并不影响其属性的修改

class Other {
    int i;
}

public class Something { 
    public static void main(String[] args) { 
            Other o = new Other(); 
            new Something().addOne(o);//匿名对象的方法调用,new先执行,再.操作,先有对象才能谈到方法调用
    } 
    public void addOne(final Other o) { //引用变量o是final量,这只能说明o指向Other类对象的地址不能变
            o.i++;//并不影响对象的属性值的改变
    }  
} 

4.11抽象类

  • 用abstract关键字来修饰一个类时,这个类叫做抽象类;
  • 用abstract来修饰一个方法时,该方法叫做抽象方法。
  • 抽象方法:只有方法的声明,没有方法的实现。以分号结束:abstract
  • 含有抽象方法的类必须被声明为抽象类。

抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。

不能用abstract修饰属性、私有方法、构造器、静态方法、final的方法。

  • Java允许类设计者指定:

超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。

  • 抽象类:
package com.atguigu.javase.abstracttest;

/**
 *  具体类:某种事物的抽象定义
 *  抽象类:某类不同种事物的统一抽象定义,抽象类使用abstract修饰
 *抽象类中可声明抽象方法,抽象方法只有方法签名,没有方法体,且 不能执行,只能说明具有某种行为
 *具体类不能包含抽象方法
 *抽象类不能创建对象,它是用来被继承的,子类必须重写抽象类的抽象方法,用来减轻子类负担
 *抽象类可以包含属性,方法,构造器
 */

public abstract class Pet {
	
	private String name;
	private int age;
	private double weight;
	
	public Pet() {
		
	}
	
	public Pet(String name, int age, double weight) {
		super();
		this.name = name;
		this.age = age;
		this.weight = weight;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	@Override
	public String toString() {
		return "Pet [name=" + name + ", age=" + age + ", weight=" + weight;
	}
	
	public abstract void speak();
	public abstract void eat();
	
}
  • 继承抽象类的具体类:
package com.atguigu.javase.abstracttest;

public class Cat extends Pet {
	
	private String color;

	public Cat() {
		super();
		this.color = "white";
	}

	public Cat(String name, int age, double weight,String color) {
		super(name, age, weight);
		this.color = color;
	}
	
	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	@Override
	public void speak() {//抽象方法->具体方法,这个过程叫“实现”,主要体现在{}的出现
		System.out.println("喵喵喵...");
		
	}

	@Override
	public void eat() {
		System.out.println("小猫吃鱼");
		
	}

	@Override
	public String toString() {
		return super.toString() + ",color=" + color + "]";
	}
}
  • 测试类:
package com.atguigu.javase.abstracttest;

public class PetTest {
	
	public static void main(String[] args) {
		
		Pet pet = new Cat("小黄",2,5,"黄色");//多态引用,可以设置抽象类引用变量
		pet.eat();//抽象类引用变量一定是多态引用,然后进行虚拟方法调用
		pet.speak();
		System.out.println(pet);
	}
}

4.12接口

接口的用途是用来定义不同类的共同方法特征。比如超人和苍蝇都有飞行方法,就可以定义出一个接口Flyer,与类是并列的关系,所有的方法都是公共抽象方法,比如起飞,飞行,着陆等,接口中的所有属性均被视全局常量。

  • 具体类(子类)可以实现接口(父类) ,并实现接口中的全部抽象方法;
  • 具体类适用父接口的多态;
  • 接口也可以继承其它接口。抽象类也可以继承接口,但是具体类必须实现接口!
  • 用interface来定义。
  • 接口中的所有成员变量都默认是由public static final修饰的。
  • 接口中的所有方法都默认是由public abstract修饰的。
  • 接口没有构造器。
  • 接口采用多继承机制。

接口:

package com.atguigu.javase.interfacetest;

/**
 * 具体类:某种具体事物的定义
 * 抽象类:某类不同种事物的抽象定义
 * 接口:不同类不同种的共同行为的抽象定义
 * 		接口中的方法都是公共抽象方法,哪怕不加public abstract也一样,所以不用加
 * 		接口中的属性都是公共静态final属性,哪怕不加public static final也一样,所以不用加,所以必须初始化!
 * @author ZXF
 *
 */

public interface Flyer {
	
	int num = 1;
	
	void takeOff();
	
	void fly();
	
	void land();
}

继承抽象类并实现接口的具体类

package com.atguigu.javase.interfacetest;

import java.io.Serializable;

import com.atguigu.javase.abstracttest.Pet;

public class Bird extends Pet implements Flyer,Serializable {//可以直接先继承父类再实现多个接口,用接口实现了多重继承!!!!
	//为什么接口可以多重继承又不担心同名方法冲突呢?因为接口中只有抽象方法,没有方法体,所以同名时不会优先继承它,而是优先继承类中的具体方法
	//和父类的关系更近一些,和接口的关系稍远
	private int flySpeed;

	public Bird() {
		super();
		this.flySpeed = flySpeed;
	}

	public Bird(String name, int age, double weight,int flyspeed) {
		super(name, age, weight);
		this.flySpeed = flySpeed;
	}

	public int getFlySpeed() {
		return flySpeed;
	}

	public void setFlySpeed(int flySpeed) {
		this.flySpeed = flySpeed;
	}

	@Override
	public String toString() {
		return super.toString() + ",flySpeed=" + flySpeed + "]";
	}
	
	@Override
	public void speak() {
		System.out.println("喳喳喳...");
	}
	@Override
	public void eat() {
		System.out.println("小鸟吃虫");
	}
	@Override
	public void takeOff() {
		System.out.println("小鸟起飞,so easy");
	}
	
	@Override
	public void fly() {
		System.out.println("天高任鸟飞");
	}
	
	@Override
	public void land() {
		System.out.println("我喜欢电线杆");
	}
}

测试类:

public static void main(String[] args) {
		Pet pet = new Bird("小飞",2,0.2,30);
		
		pet.eat();
		pet.speak();
		

		Flyer flyer = (Flyer)pet;//注意pet的对象是谁,是bird呀,Flyer作为被实现的接口,也是父类,所以bird子类也可以造型成Flyer父类形态
		flyer.takeOff();
		flyer.fly();
		flyer.land();
		flyer.toString();//这个方法不能执行,因为接口中没有toString方法
	}

易错:

//1
interface A{ 
	int x = 0;
} 
class B{ 
	int x =1;
} 
class C extends B implements A { 
	public void pX(){
		System.out.println(x);//x发生了冲突,不知道访问的是哪个x了
		System.out.println(super.x);//这代表访问父类的x
		System.out.println(A.x);//这代表访问接口的常量x
	}
	public static void main(String[] args) {
		new C().pX();
	}  
} 
//2
interface Playable {
    void play();
}
interface Bounceable {
    void play();
}
interface Rollable extends Playable, Bounceable {
    Ball ball = new Ball("PingPang");//这里虽是接口但是不妨碍它new出对象
    //但注意这里其实是一个ball类的属性,且是全局常量
}

class Ball implements Rollable {
    private String name;
    public String getName() {
        return name;
    }
    public Ball(String name) {
        this.name = name;        
    }
   public void play() {
        ball = new Ball("Football");//所以这里给全局常量重复赋值,会报错
        System.out.println(ball.getName());
    }
}

接口表达的是能力,如果某类具有这样的能力,也可以认为这个类达到了接口的标准和规范;

接口规范的目的是越多的子类实现越好。

4.12.1代理模式

代理类和被代理类实现一个公共的接口,把代理对象当成被代理对象来用,并且可以在创建对象时统一被接口多态引用。

package com.atguigu.javase.interfacetest;

/**
 * 代理模式:把代理对象当成被代理对象来使用
 * 
 * 场景1:无法直接使用被代理对象时
 * 场景2:增强被代理对象的方法,又不能修改被代理类,可以通过代理类增强
 * @author ZXF
 *
 */

interface HouseRent {
	void rent();
}

class FangDong implements HouseRent {

	@Override
	public void rent() {
		System.out.println("我有好房子出租,但是只能银行卡转账");
	}
}

class FangDong2 implements HouseRent {

	@Override
	public void rent() {
		System.out.println("我的房子不太好,但是便宜");
	}
}

class LianJia implements HouseRent {
	
	//关联被代理对象
	private  FangDong2 fd = new FangDong2();//这就像一个切面,可以随时调换被代理对象,方便操作
	
	@Override
	public void rent() {
		//准备租房子
		System.out.println("先交中介费");
		fd.rent();//被代理对象的方法调用
		System.out.println("交房租可以支持微信转账啦");//可以对被代理对象的方法进行增强
	}
}

public class ProxyTest {
	
	public static void main(String[] args) {
		//客户要租房
		//new FangDong();
		HouseRent hr = new LianJia();//把LianJia类和FangDong类都看成接口类型,可以统一类型,这就是面向接口编程
		hr.rent();//面向接口编程可以对子类解耦,只关注接口中的方法,不关注子类的特有方法
		
	}
}

4.12.2工厂方法

概述:

定义一个用于创建对象的接口,让子类决定实例化哪一个类。FactoryMethod使一个类的实例化延迟到其子类。

适用性:

1. 当一个类不知道它所必须创建的对象的类的时候

2. 当一个类希望由它的子类来指定它所创建的对象的时候

3. 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候

总结:

FactoryMethod模式是设计模式中应用最为广泛的模式,在面向对象的编程中,对象的创建工作非常简单,对象的创建时机却很重要。FactoryMethod解决的就是这个问题,它通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。

package com.atguigu.javase.interfacetest;

interface Worker {
	void work();
}

class Teacher implements Worker {//用了接口之后
	public void work() {
		System.out.println("老师在工作");
	}
}
class Student implements Worker {
	public void work() {
		System.out.println("学生在学习");
	}
}

class Factory {
	public static Worker geTeacher() {//返回值类型可以统一为Worker类型了
		return new Teacher();
	}
	
	public static Worker getStudent() {
		return new Student();
	}
}

public class FactoryTest {
	
	public static void main(String[] args) {
		//对象的获取:new,工厂方法,反序列化,直接开辟空间
		
		Runtime runtime = Runtime.getRuntime();//通过方法获取对象
		Worker w1 = Factory.geTeacher();//同时接收值的类型也可以统一成Worker类型,就不必去追究被创建的对象是什么子类的了
		Worker w2 = Factory.getStudent();
	}
}

Java三部曲之JavaSE基础_第24张图片

4.13内部类

在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。

Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。

Inner class的名字不能与包含它的类名相同;

Inner class可以使用外部类的私有数据,因为它是外部类的成员,同一个类的成员之间可相互访问。而外部类要访问内部类中的成员需要:内部类.成员或者内部类对象.成员。

分类:成员内部类(static成员内部类嵌套类和成员内部类)

       局部内部类(不谈修饰符)、匿名内部类

  • Inner class作为类的成员:

可以声明为final的;

和外部类不同,Inner class可声明为private或protected;

Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;

  • Inner class作为类:

可以声明为abstract类,因此可以被其它的内部类继承;

【注意】非static的内部类中的成员不能声明为static的,只有在外部类或static的内部类中才可声明static成员。

  • 匿名内部类

匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类。

new 父类构造器(实参列表)|实现接口(){

    //匿名内部类的类体部分

}

package com.atguigu.javase.innerclass;

import org.omg.CORBA.PUBLIC_MEMBER;

/**
 * 成员内部类:声明在类中方法外
 * 		没有被static修饰,称普通内部类
 * 		被static修饰的,称为嵌套类
 * 局部内部类:声明在方法中
 * 		普通用法,普通局部内部类
 * 		匿名内部类
 * 
 * @author ZXF
 *
 */

class Outer {
	
	private int id = 10;
	private String name = "Outer.name";
	
	//普通内部类,隶属于外部类的对象的
	public class Inner1 {//内部类也是一种对象关联的形式,但是和别的对象关联不同的是,内部类可以访问私有成员
		private int id = 100;
		
		public void inner1Test() {
			System.out.println(id);//与下面一样
			System.out.println(Inner1.this.id);//这个this就是内部类的对象,当内部类和外部类变量冲突时,默认的是内部类的变量
			System.out.println(Outer.this.id);//通过类限定可以直接访问外部类的对象的属性
			System.out.println(name);//但是没有冲突时,就可以直接打印出来了
			
			outerTest2();//可以直接访问外部类中的方法,因为内部类也是一个成员,成员互访
		}
	}
	
	public void outerTest1() {
		Inner1 inner1 = this.new Inner1();//相当于成员互访,实际上是省略了this的,这个this就是指外部类的对象
		inner1.inner1Test();
	}
	
	public void outerTest2() {
		System.out.println("outerTest2()...");
	}
	//嵌套类
	public static class Inner2 {//这其实就是相当于其他的普通类
		public static String name = "Inner2";
		
		public Inner2(int age) {
			this.age = age;
		}
		public static void staticTest() {
			System.out.println("inner static test...");
		}
		private int age;
		public void inner2Test() {
			System.out.println("inner2Test...");
		}
	}

}

interface I1 {
	
	void test1();
}

public class InnerClassTest {
	
	
	public static void main(String[] args) {
		//匿名内部类,因为没有类名,必须在声明的同时就创建对象,并且多态引用
		I1 i1 = new I1() {//只能在声明类的时候创建一个对象,右边{}里巴拉巴拉一堆,指的是匿名内部类的内容,它其实就是子类
			//类体的部分就是接口的实现子类的类体
			@Override
			public void test1() {
				System.out.println("匿名内部类的实现方法体");
			}
		};
		i1.test1();
		
		new InnerClassTest().callInner(new I1() {//new InnerClassTest()创建外部类的匿名对象.callInner(调用成员方法
			@Override//将匿名内部类的匿名对象作为对象参数传入
			public void test1() {//callInner方法再去调用匿名内部类的匿名对象的方法test1
				System.out.println("匿名内部类作为参数传给外部类的成员方法,该方法再调用匿名内部类的方法!!");
			}
		});
		
		new I1() {
			@Override
			public void test1() {
				System.out.println("匿名内部类的匿名对象,仅适用于一次性调用!!");
			}
		}.test1();//仅适用于一次性调用
		
		
		Outer outer1 = new Outer() {};//用父类构造器创建匿名内部类
		System.out.println(outer1.getClass());//class com.atguigu.javase.innerclass.InnerClassTest$2,这是Outer的子类了
		//所以匿名内部类一定是子类,或实现类
	}
	
	public void callInner(I1 i) {
		i.test1();
	};
	
	public static void main2(String[] args) {
		//在方法中声明的内部类
		class LocalClass1 {//普通局部内部类,没有修饰符,然后范围小一点~
			private int id;
			private String name;
			public int getId() {
				return id;
			}
			
			
			public LocalClass1(int id, String name) {
				super();
				this.id = id;
				this.name = name;
			}


			public void setId(int id) {
				this.id = id;
			}
			public String getName() {
				return name;
			}
			public void setName(String name) {
				this.name = name;
			}
			@Override
			public String toString() {
				return "LocalClass1 [id=" + id + ", name=" + name + "]";
			}
		};//这里要加一个分号,因为它也类似于一个语句
		LocalClass1 localClass1 = new LocalClass1(1,"aa");
		System.out.println(localClass1.getName());
	}
	
	public static void main1(String[] args) {
		Outer outer = new Outer();
		outer.outerTest1();
		
		//直接创建内部类对象
		//Outer.Inner1 oi1 = new Outer.Inner1();//不能这样操作,因为类.操作的只能是静态成员
		//Outer.Inner1 oi1 = new outer.Inner1();//那换成对象去.操作,又会被编译器识别为包名,所以也不行
		Outer.Inner1 oi1 = outer.new Inner1();//实际上,要用外部类的对象去限定new!!!
		
		//嵌套类的静态成员不需要创建对象就可以用类访问,但是也要进行外部类的限定
		Outer.Inner2.staticTest();
		Outer.Inner2 oi2 = new Outer.Inner2(30);//因为是静态成员,直接进行类.操作
	}
}

 第五章:枚举与注解

5.1枚举

若枚举只有一个成员, 则可以作为一种单例模式的实现方式;

当一个类的对象是可数的情况下,就可以使用枚举(比如性别)

package com.atguigu.javase.enumtest;

enum TrafficSingnal {
	GO(10),STOP(23),CAUTION(13);//这玩意其实就是TrafficSingnal类的对象,且默认调用无参构造器,这里可以给调用有参构造器
	
	private int seconds;//可以自己加上新属性
	
	private TrafficSingnal() {
	}

	private TrafficSingnal(int seconds) {//所以可以给一个有参构造器
		this.seconds = seconds;
	}

	public int getSeconds() {
		return seconds;
	}

	public void setSeconds(int seconds) {
		this.seconds = seconds;
	}
	
	public String toString() {
		return super.toString() + ",时间:" + seconds;
	}
}


public class EnumTest {
	
	public static void main(String[] args) {
		TrafficSingnal ts = TrafficSingnal.CAUTION;//从类中获取枚举对象
		System.out.println(ts);
		
		ts = TrafficSingnal.valueOf("STOP");//用方法获取枚举对象
		System.out.println(ts);
		
		TrafficSingnal[] values = TrafficSingnal.values();
		int n = Integer.parseInt(args[0]);//run as->run configuration->arguments->program argument->输入要赋值的命令行参数
		System.out.println(values[n]);//上面输入多少,n就是多少
		
		switch (ts) {//分支,括号里变量类型:非long整数,字符串,枚举
			case GO://case后面跟的是常量
				System.out.println("绿灯");
				break;
			case STOP:
				System.out.println("红灯");
				break;
			case CAUTION:
				System.out.println("黄灯");
				break;
		}
	}
	
}

5.2注解

从 JDK5.0 开始,Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解);

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理. 通过使用Annotation,程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息;

Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation的 “name=value”对中;

Annotation 能被用来为程序元素(类, 方法, 成员变量等) 设置元数据。

package com.atguigu.javase.annotationtest;

/** 
 * 注解:是一种特殊的注释,不参与程序执行,特殊之处是编译器和JVM都能识别
 *@Override 告诉编译器,要完成覆盖,提醒编译器提前检查,只能修饰方法
 *@Deprecated 作用是警告使用者,修饰的目标过期了,别用了,可以修饰类、属性、方法、构造器、形参、局部变量
 *@SuppressWarnings 作用是抑制编译器警告,需要传参数,可以是一个值或数组
 *2个元注解:约束注解的注解
 *@Target(ElementType.Method) 指定自定义注解可以修饰的类型(TYPE类、FIELD属性、METHOD方法、CONSTRUCTOR构造器、PARAMETER形参、LOCAL_VARIABLE局部变量)
 *@Retention 作用是约束注解的停留期,常量RetentionPolicy.SOURCE表示停留期只在源码中,RetentionPolicy.CLASS表示注解可以停留在class文件中
 *RetentionPolicy.RUNTIME表示注解停留期在运行时,这样就可以通过反射处理
 */
//自定义注解:可以修饰类、属性、方法、构造器、形参、局部变量
@Target(ElementType.TYPE)
@interface MyAnnotation {//本质就是接口
	int id();//属性的写法与方法类似
	String name() default "缺省值";
}

@Deprecated
@MyAnnotation(id = 1)
class Person {
	
	@Deprecated
	private String name;
	private int age;
	private String gender;
	
	@SuppressWarnings({"unused","null"})
	
	public Person() {
		@Deprecated int n;
		int[] arr = null;
		System.out.println(arr.length);
	}
	
	@Deprecated
	public Person(String name, int age, String gender) {
		super();
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
	@Deprecated public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]";
	}
	
	
}

第六章:异常处理

异常的堆栈式抛出机制:

和方法return后会弹栈一样,方法出现异常也会弹栈,并且其异常对象会抛给下一层的栈里的方法,接着弹栈接着抛出,直到main方法也弹栈,异常抛给虚拟机,虚拟机再抛给操作系统,操作系统发现异常,立即终止程序。但是异常出现之后的方法虽然弹栈但都没有真正执行。

package com.atguigu.javase.exception;

import java.sql.SQLException;

import javax.management.RuntimeErrorException;

/**
 * 异常:程序在运行时出现的非正常状况,会导致程序崩溃
 * 分类:
 * 	按照程度来分:
 * 		Error:虚拟机都无法处理的错误状况
 * 		Exception:一般性的问题
 * 	按处理方式来分:
 * 		受检异常:程序中必须进行处理的异常,否则编译不通过,由Exception类表示
 * 			Exception及其子类,但是RuntimeException及其子类除外
 * 		非受检异常:程序中不是必须处理的异常或致命性错误异常,由RuntimeException类或Error类表示
 * 			Error及其子类:太严重了
 * 			RuntimeException及其子类:太轻微了
 * 异常处理:
 * 	异常捕获:try-catch-finally,一般是从范围小的异常开始抛,异常被捕获后就不会导致程序崩溃了,可以跳过异常程序继续执行,相当于保护了后面的程序
 * 		try {
 * 			可能抛出异常的语句
 * 		} catch (可能的异常类型1 引用变量) {//如果出现了该异常,就执行catch
 * 			通过引用变量处理异常对象
 * 		} catch (可能的异常类型2 引用变量) {
 * 			通过引用变量处理异常对象
 * 		} ... 
 * 		} catch (Exception e) {//捕获剩余的异常
 * 			剩下的未捕获的异常在这里处理
 * 		} finally {
 * 			无论发生什么都要执行这个程序//通常在这里释放不在GC区中的资源,比如硬盘、网络等硬件资源,为了保证不要发送资源泄漏
 * 		}
 * 	主动抛出异常:这个抛出指的是在方法签名中使用throws,给出受检异常的抛出警告,异常可以是一个列表
 * 		在方法中使用throw异常对象,方法一旦执行了throw和执行return效果一样,方法就会结束,只不过throw是异常结束
 * 	先捕获再抛出:把各种已知未知的异常,包装到自定义异常中,便于统一处理
 * 		在方法中尝试执行某代码,若真的出现异常,把这个异常关联到自定义异常对象中,再抛出自定义异常
 * 异常处理的选择:
 * 	会影响到栈的方法尽量捕获,普通方法尽量抛出
 * 如果代码有潜在风险,尽量先捕获后抛
 * 如果代码没有风险,但是有时候不满足方法继续的条件时,直接抛出
 * @author ZXF
 *
 */
//自定义异常,必须继承Exception,并提供两个构造器,这是受检异常(它是Exception的子类且不是RuntimeException的子类)
class DivideByZeroException extends Exception {

	public DivideByZeroException(String message) {
		super(message);
	}

	public DivideByZeroException(Throwable cause) {
		super(cause);
	}
}

public class ExceptionTest {
	
	public static int divide(int x,int y) throws DivideByZeroException {
		
		try {
			return x / y;
		} catch (Exception e) {//先捕获异常
			throw new DivideByZeroException(e);//传参给构造器DivideByZeroException(Throwable cause),包装已知异常
		}
	}
	
	public static void main(String[] args) {
		System.out.println("main begin");
		
		try {
			System.out.println(divide(10, 0));
		} catch (DivideByZeroException e) {
			System.out.println(e);//再抛出异常
		}
		
		System.out.println("main end");
	}
	
	public static int divide1(int x,int y) throws DivideByZeroException {//在方法签名上加上抛出异常警告就可以了
		if (y == 0) {//判断可能出现的异常情形
			throw new DivideByZeroException("自定义异常,出现0除错误");//受检异常,必须处理,也就是方法上必须提前添加抛出异常的警告
		}
		return x / y;
	}
	
	public static void main3(String[] args) {//main方法当然也可以选择抛出异常,但是容易杀死整个栈,所以建议采用捕获的方式处理异常
		System.out.println("main begin");
		try {
			System.out.println(divide1(10, 2));
			System.out.println(divide1(10, 0));
		} catch (DivideByZeroException e) {
			System.out.println(e);
		}
		System.out.println("main end");
	}
	
	public static int divide2(int x,int y) {
		if (y == 0) {//判断可能出现的异常情形
			RuntimeException runtimeException = new RuntimeException("我是异常,出现0除错误");//new出异常对象,描述异常情况,这里是非受检异常
			throw runtimeException;//如果出现就抛出,注意这里的对象必须都是Throwable的子类对象
		}
		return x / y;
	}
	
	public static void main2(String[] args) {
		System.out.println("main begin");
		try {
			System.out.println(divide2(10, 2));
			System.out.println(divide2(10, 0));
		} catch (RuntimeException e) {//同样要来捕获这个异常
			System.out.println(e);
		}
		
		System.out.println("main end");
	}
	
	public static void main1(String[] args) {
		System.out.println(test());
		
		try {//可能抛出异常的语句
			int n = Integer.parseInt(args[0]);//若出现异常,这里就不能执行了
			System.out.println("n:" + n);
			int[] arr = null;
			System.out.println(arr.length);//出现空指针异常
		} catch (NumberFormatException e) {//可能出现的异常:数据类型异常NumberFormatException
			System.out.println(e.getMessage());//出现异常就会打印返回的异常信息,返回的是字符串
		} catch (ArrayIndexOutOfBoundsException e) {//可能出现的异常ArrayIndexOutOfBoundsException
			e.printStackTrace();//函数直接打印异常类名和异常信息,以及异常出现在程序中的位置,返回值void
		} catch (Exception e) {//最后选择一个可以捕获任意异常的类型
			System.out.println(e);//这其实是e的toString
		} finally {//无论try-catch发生什么都要执行这个程序
			System.out.println("无论发生什么都要执行这个程序");
		}
		
		System.out.println("main end");
	}
	
	public static int test() {
		int n = 10;
		try {
			return n;//return也挡不住finally的执行,解析,若是从这里弹栈,return操作会先把n的值存放进临时空间,然后准备返回其值
			//但此时被finally拦截,去执行finally里的语句了,n++后n变成了11,它执行完之后终于可以弹栈了,那返回多少呢,实际上返回的是临时空间里的值
			//也就是之前保存的10才是被返回的值,finally中不能真正修改前面的返回值,但如果在fianlly中打印该值,却是可以打印出修改后的值的
		} finally {
			n++;
			return n;//那如果这里也有return,应该从这里弹栈还是从上面弹栈呢
			//实际上,finally有return的就从fianlly里弹栈,此时finally中的修改就有效啦,否则finally里n的处理也无法影响前面返回的n的值
		}
	}
}

package com.atguigu.javase.exception;

public class TestException {

	public static void main(String[] args) {
		try {
			int n1 = Integer.parseInt(args[0]);//传入参数正确就不会有问题,否则会报超出数组范围的异常
			int n2 = Integer.parseInt(args[1]);
			int n3 =  n1 / n2;
			/*
			System.out.println(2.0 / 0);//Infinity
			System.out.println(0.0 / 0);//Nan:Not A Number
			System.out.println(0 / 0);//出异常ArithmeticException,抛出异常后程序就终止了
			*/
			System.out.println(n1 + "/" + n2 + "=" + n3);
		} catch (NumberFormatException e) {
			System.out.println(e.getMessage());
		} catch (ArrayIndexOutOfBoundsException e) {
			e.printStackTrace();//ArrayIndexOutOfBoundsException: 1,getMessage只能获得冒号后的信息,printStackTrace可以打印整个栈迹
		} catch (ArithmeticException e) {
			e.printStackTrace();//打印栈迹
		} catch (Exception e) {//抛出其他异常
			System.out.println(e);//打印异常类型
		}
		
		
		System.out.println("重要代码");
	}
}

第七章:Java常用类

API文档使用:**able是指接口,**exception,**error是指异常。

7.1包装类

就是将基本数据类型包装成引用型的对象。

Java三部曲之JavaSE基础_第25张图片

package com.atguigu.javase.wrapper;

import org.junit.Test;

public class WrapperTest {
	
	@Test
	public void test2() {
		Integer i = new Integer(1);
		Integer j = new Integer(1);
		System.out.println(i == j);//对象的地址不同
		
		Integer m = 1;//自动装箱,调用的是Integer.valueOf();
		Integer n = 1;//如果装箱值在-128~127之间,会取CACHE缓冲区中,缓冲对象数组中的一个
		System.out.println(m == n);
		
		Integer x = 128;
		Integer y = 128;
		System.out.println(x == y);
	}
	
	@Test
	public void test1() {
		int n = 200;
		Integer obj1 = new Integer(n);
		Integer obj2 = n;
		
		System.out.println(obj1 = obj2);
		
		//拆箱
		int v1 = obj1.intValue();
	}
}

7.2String类

常量对象:字符串常量对象是用双引号括起的字符序列,"12.11","abd"等

字符串中的字符采用unicode编码,一个字符占2个字节,其实就是一个Unicode字符序列

字符串常量存放在永久区的常量池中

创建对象的几种方式:

String  s1 = new String();

String  s2 = new String(String original);

String  s3 = new String(char[] a);

String  s4 = new String(char[] a,int startIndex,int count);

package com.atguigu.javase.string;

import org.junit.Test;

/**
 * 字符串:内容不可改变,安全稳定,Unicode字符的序列,任何对字符串的修改都一定会产生新的字符串对象
 * 底层使用了char数组,char[]来保存字符,字符串处理与下标有关
 * String的常用方法:
 * 	public int length() //获取字符串长度string.length();
	public char charAt(int index) //获取参数指定的下标位置处的字符string.charAT(10);
	public char[] toCharArray() //字符串还原成字符数组,获取的内部数组的一个副本,修改副本不影响源数组
		System.arraycopy(value, 0, result, 0, value.length);
		//第一个参数是源数组,
		 * 第二个参数是源数组开始下标
		//第三个参数是目标数组,
		 * 第四个参数是目标数组的开始复制的下标,
		 * 第五个参数是总共要复制的元素个数
		 * 效果相当于
		 for (int i = 0;i < value.length;i++) {
		 	result[i] = value[i]
		 }
	public boolean equals(Object anObject) //比较内容
	public boolean equalsIgnoreCase(String anotherString) //忽略大小写,判断内容是否相等
	
	public int compareTo(String anotherString) //比较Unicode码大小
	public int compareToIgnoreCase(String str) //比较Unicode码大小,但是忽略大小写
	
	public int indexOf(String s) //获取参数中传入的子串在当前字符串中首次出现的下标值(指的是该子串的首字符的下标)string.indexOf("喜欢") => 13
	public int indexOf(String s ,int startpoint)//获取第二个喜欢:string.indexOf("喜欢",14) => 18,如果搜索失败返回-1
	public int lastIndexOf(String s)//从右向左搜索子串的下标,string.lastIndexOf("喜欢") => 25
	public int lastIndexOf(String s ,int startpoint)
	
	public boolean startsWith(String prefix) //判断字符串是否以参数中的子串开始
	public boolean endsWith(String suffix) //判断字符串是否以参数中的子串结束
	
	public String substring(int start,int end) //从当前字符串中截取子串,start表示开始下标,end表示结束下标,返回值不包含end的下标,即[start:(end-1)]
	public String substring(int startpoint) //从start开始截,直到末尾 == string.substring(start,string,length());
	
	public String replace(char oldChar,char newChar) //替换所有旧字符为新字符
	public String replaceAll(String old,String new) //替换老子串为新子串
	
	public String trim() //修剪字符串首尾的空白字符,(码值小于等于32的字符,包括space,回车,tab,删除等控制符)
	
	public String concat(String str) //拼接字符串,于+一样的效果
	public String toUpperCase() //变大写
	public String toLowerCase() //变小写
	public String[] split(String regex) //对字符串进行切片,用regex作为分隔符,进行切割
 * @author ZXF
 *
 */
public class StringTest {
	
	@Test
	public void test1() {
		String s1 = "abc";
		s1 = s1 + 100;//s1 + 100产生一个新的字符串,只是将这个新字符串对象的地址刷给该变量而已
		
		String s2 = new String();
		String s3 = "";//他俩不等,因为""是常量,它在常量区,new出来的对象则在GC区,肯定不同
		
		char[] arr = {'a','1','大','?','好'};
		String s4 = new String(arr);//直接传入字符数组
		
		String s5 = new String(arr,2,2);//从arr字符数组中,从索引下标2开始取,取2个元素
		System.out.println(s5);
	}
	@Test
	public void test2() {
		String s1 = "atguigu"; 	
		String s2 = "java";
		String s4 = "java";
		String s3 = new String("java");
		System.out.println(s2 == s3);//false
		System.out.println(s2 == s4);//true
		System.out.println(s2.equals(s3));//true
		
		String s5 = "atguigujava";
		String s6 = (s1 + s2).intern();//intern()是将对象转换成常量值,如果不存在该常量,就压进常量池,如果常量池中已存在该常量,就会自动返回该常量的地址
		System.out.println(s5 == s6);//true
		System.out.println(s5.equals(s6));//true
		
		String str1 = "JavaEE";
		str1 = str1 + "Android"; // “java” + “Android”,变量+常量还是压进GC区,还是变量
		System.out.println(str1);
	}
	@Test
	public void test3() {
		String string = "  abcABXXYY 我喜欢你,你喜欢我吗?我不喜欢你 qqyyZZ123  ";
		System.out.println(string.length());
		System.out.println(string.charAt(10));
		
		//遍历字符串
		for (int i = 0;i < string.length();i++) {
			System.out.print(string.charAt(i));
		}
		System.out.println();
		//调用反转字符串函数
		System.out.println(exper1(string));
		System.out.println(exper2(string));
		System.out.println(exper3(string));
	}
	//反转字符串
	public String exper1(String string) {
		String string_reverse = "";
		for (int i = string.length() - 1;i >= 0;i--) {
			string_reverse += string.charAt(i);
		}
		return string_reverse;
	}
	public String exper2(String string) {
		String string_reverse = "";
		for (int i = 0;i < string.length();i++) {
			string_reverse = string.charAt(i) + string_reverse;//让第一个字符不断往右边挤,直到将字符串完全颠倒
		}
		return string_reverse;
	}
	public String exper3(String string) {
		char[] charArray = string.toCharArray();//这其实是一个数组的复制,生成一个新的数组,不会改变原数组的值
		for (int i = 0;i < charArray.length / 2;i++) {
			//i和length - 1 - i
			char tmp = charArray[i];
			charArray[i] = charArray[charArray.length - 1 - i];//将对称位置的元素交换位置,循环次数变少了
			charArray[charArray.length - 1 - i] = tmp;
		}
		String string2 = new String(charArray);
		return string2;
	}
	@Test
	public void test4() {
		String string = "  abcABXXYY 我喜欢你,你喜欢我吗?我不喜欢你 qqyyZZ123  ";
		int indexOf = string.indexOf("ABXX");
		System.out.println(indexOf);
		
		int indexOf2 = string.indexOf("ABC");
		System.out.println(indexOf2);//找不到该元素,返回-1
		
		//获取一个字符串在另一个字符串中出现的次数
		//比如:"ab"在"abkkcadkabkebfkabkskab"中出现的次数
		System.out.println(getCount1("abkkcadkabkebfkabkskab", "bk"));
		System.out.println(getCount("abkkcadkabkebfkabkskab", "bk"));
		
		System.out.println(string.startsWith("  "));
		System.out.println(string.startsWith("abc"));
		
		System.out.println(string.endsWith("  "));
		System.out.println(string.endsWith("abc"));
		
		System.out.println(string.substring(12,16));
		
		//将"abcdefg"部分反转为"abfedcg"
		System.out.println(partReverse("abcdefg", 2, 6));
		
		System.out.println(string.replace(' ', '#'));//注意这里替换为string有变化吗?没有没有没有,只是增加了一个新的字符串而已
		
		System.out.println(string.replaceAll("喜欢", "讨厌"));
		
		//消除所有空格
		System.out.println(string.replaceAll(" ", ""));
		
		//变大写和变小写
		System.out.println(string.toUpperCase());
		System.out.println(string.toLowerCase());
	}
	@Test
	public void test5() {
		
		String string = "\t\r\n   \t\t\t\r\r\nabcdefg  你好吗  123 \t\t\r\b";
		System.out.println(string);
		System.out.println(string.trim());//消除首尾的空白字符,空白字符不仅仅是空格字符哦
		
		System.out.println(trimSimulate(string));
	}
	
	@Test
	public void test6() {
		
		String string = "abc,你好吗,不好,12345,qqqqq";
		String[] splits = string.split(",");//以","为分隔符进行切割
		for (int i = 0;i < splits.length;i++) {
			System.out.println(splits[i]);
		}
		
		String path = "D:\\MyWork\\Program\\jdk1.8.0_221\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Windows\\System32\\OpenSSH\\;D:\\pgSQL\\pg10\\bin;D:\\matlab\\R2021a\\runtime\\win64;D:\\matlab\\R2021a\\bin;D:\\matlab\\R2021a\\polyspace\\bin;D:\\Program Files\\MySQL\\MySQL Server 5.5\\bin;D:\\MyWork\\Program\\MySQL Server 5.5\\bin;C:\\Users\\ZXF\\AppData\\Local\\Microsoft\\WindowsApps;";
		String[] pathSplits = path.split(";");
		System.out.println(pathSplits[0]);
	}
	
	@Test
	public void test7() {
		String s1 = "abc";
		String s2 = "ABC";
		
		System.out.println(s1.equals(s2));
		System.out.println(s1.equalsIgnoreCase(s2));//忽略大小写,看内容是否相等
	}
	
	//获取一个字符串在另一个字符串中出现的次数
	//比如:"ab"在"abkkcadkabkebfkabkskab"中出现的次数
	public int getCount(String string,String childString) {
		
		int count = 0;
		int index = 0;
		while (true) {
			index = string.indexOf(childString, index);//直接将index刷成子串所在位置的索引值
			if (index == -1) {//判断是否还存在该子串
				break;
			}
			count++;
			index++;//直接让index自增1,进行下一轮的搜索与判断
		}
		return count;
	}
	
	public int getCount1(String string,String childString) {
		
		if (string.indexOf(childString,0) == -1) {
			return -1;
		}
		int realCount = 1;
		for (int i = 0;i < string.length();i++) {
			if (string.indexOf(childString,i) == -1) {
				break;
			}
			int tmp = string.indexOf(childString,i);
			if (string.indexOf(childString,i + 1) != tmp && string.indexOf(childString,i + 1) != -1) {
				realCount++;
			}
		}
		return realCount;
	}
	
	//字符串反转:将"abcdefg"部分反转为"abfedcg"
	public String partReverse(String string,int begin,int end) {
		//包含begin,不包含end
		//把字符串切成3段
		String s1 = string.substring(0,begin);
		String s2 = string.substring(begin, end);
		String s3 = string.substring(end);
		//将要反转的子串进行反转
		char[] charArray = s2.toCharArray();
		for (int i = 0;i < charArray.length / 2;i++) {
			char tmp = charArray[charArray.length - 1 - i];
			charArray[charArray.length - 1 - i] = charArray[i];
			charArray[i] = tmp;
		}
		s2 = new String(charArray);
		
		return s1 + s2 + s3;
	}
	//模拟trim方法,消除两端的空白字符
	public String trimSimulate(String string) {
		
		int begin = 0;//初始值就是没有做调整的情况下,不做截取
		int end = string.length() - 1;
		
		for (int i = 0;i < string.length();i++) {
			char ch = string.charAt(i);
			if (ch > 32) {
				begin = i;
				break;
			}
		}
		
		for (int i = string.length() - 1;i >= 0;i--) {
			char ch = string.charAt(i);
			if (ch > 32) {
				end = i;
				break;
			}
		}
		if (begin == 0 && end == 0) {
			return "";
		} else {
			return string.substring(begin,end + 1);
		}
	}
}

7.3StringBuffer类与StringBuilder类

String使用陷阱:

string s="a"; //创建了一个字符串

 s=s+"b"; //实际上原来的"a"字符串对象已经丢弃了,现在又产生了一个字符串s+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。

所以引入可以不用频繁创建对象的StringBuffer类与StringBuilder类(二者在使用上没有区别,区别在于效率与线程安全性)

package com.atguigu.javase.string;

import org.junit.Test;

/**
 * String:不可变字符序列,任何的修改都是产生新对象
 * StringBuffer:可变字符序列、效率低、线程安全,但任何的修改都不会产生新对象,都是在原对象上进行修改
 * 		它就像一个容器,可以保存字符的容器,底层仍使用字符数组
 * 		StringBuffer的构造器:
 * 		StringBuffer():初始容量为16的字符串缓冲区
 * 		StringBuffer(int size):构造指定容量的字符串缓冲区
 * 		StringBuffer(String str):将内容初始化为指定字符串内容
 * 		函数:
 * 		append(Object obj):在末尾添加多种数据类型的元素
 * 		insert(int index,Object obj):可以在指定索引处插入多种类型的数据
 * 		reverse():反转字符串
 * 		delete(int start,int end):删除start~end之间的元素
 * 		charAt(int n):检索n处的值
 * 		setCharAt(int n,char ch):在n处替换指定字符
 * 		replace(int start,int end,String str):将start~end之间的子串,替换为新串str
 * 		indexOf(String str):获取子串str首次出现的下标值
 * 		substring(int start,int end):于String相同
 * 		length():返回字符串长度
 * StringBuilder(JDK1.5):与StringBuffer一样,也是可变字符序列,但是效率高、线程不安全,不过这个是更新的版本,我们一般采用它
 * @author ZXF
 *
 */
public class StringBufferTest {
	
	@Test
	public void test2() {
		//声明3个字符串,用第一个为参数设计StringBuffer对象,第二个串串在末尾,第三个串串在开头
		String s1 = "123456";
		String s2 = "abcdef";
		String s3 = "来点汉字";
		
		StringBuffer stringBuffer = new StringBuffer(s1);
		stringBuffer.insert(0, s3).append(s2);
		System.out.println(stringBuffer);
	}
	
	@Test
	public void test1() {
		StringBuilder stringBuilder = new StringBuilder();//提供初始容量为16的字符串缓冲区
		stringBuilder.append("abc");
		stringBuilder.append(1);
		stringBuilder.append('x');
		stringBuilder.append('y');
		stringBuilder.append(true);
		stringBuilder.append(3.14);
		
		System.out.println(stringBuilder);//超过16个初试容量时,会自动扩容,新容量=老容量*2+2
		
		stringBuilder.insert(3, "一些汉字");//在下标3处插入"一些汉字"
		stringBuilder.insert(0,false);//在下标0处插入false
		
		System.out.println(stringBuilder);
		
		stringBuilder.setCharAt(0, 'F');
		
		System.out.println(stringBuilder);
		
		StringBuilder stringBuilder2 = new StringBuilder();
		stringBuilder2.append("abc").append(1).append('x').append(true).append(3.14).insert(3, "一些汉字").insert(0,false).setCharAt(0, 'F');
		//可以用以上方式一次性设计完字符串
		System.out.println(stringBuilder2);
	}
}

你可能感兴趣的:(#,web,学习)