Java概述

JavaSE课程体系介绍

JavaSE知识图解

1561379629326

JavaSE知识模块介绍

  • 第一部分:计算机编程语言核心结构:数据类型运算符流程控制数组、…
  • 第二部分:Java面向对象核心逻辑:类和对象封装继承多态抽象接口、…
  • 第三部分:JavaSE核心高级应用:集合I/O多线程网络编程反射机制、…
  • 第四部分:Java新特性:Lambda表达式函数式编程新Date/Time API接口的默认、静态和私有方法、…
  • 第五部分:MySQL/JDBC核心技术:SQL语句数据库连接池DBUtils事务管理批处理、…

计算机语言介绍(了解)

计算机语言是什么

所谓计算机编程语言,就是人们可以使用编程语言对计算机下达命令,让计算机完成人们需要的功能。

计算机语言发展

1602830735505

·第一代语言:机器语言(相当于人类的原始阶段)

机器语言由数字组成所有指令。当让你使用数字编程,写几百个数字、甚至几千个数字,每天面对的是纯数字,我大胆预测:“程序员群体100%会有精神问题”。

机器语言通常由数字串组成(最终被简化成01),对于人类来说,机器语言过于繁琐。使用机器语言,人类无法编出复杂的程序。如下为一段典型的机器码:

  1. 0000,0000,000000010000 代表 LOAD A, 16

  2. 0000,0001,000000000001 代表 ADD B, 1

  3. 0001,0001,000000010000 代表 STORE C, 16

·第二代语言:汇编语言(相当于人类的手工业阶段)

为了编程的方便,以及解决更加复杂的问题。程序员开始改进机器语言,使用英文缩写的助记符来表示基本的计算机操作。这些助记符构成了汇编语言的基础。如下是一些常见的汇编语言助记符(单词)比如:LOAD、MOVE之类,这样人更容易使用了。识别几百、几千个单词,感觉要比几百几千个数字,美妙多了。汇编语言相当于人类的手工业社会,需要技术极其娴熟的工匠,但是开发效率也非常低。

汇编语言虽然能编写高效率的程序,但是学习和使用都不是易事,并且很难调试。另一个复杂的问题,汇编语言以及早期的计算机语言(Basic、Fortran等)没有考虑结构化设计原则,而是使用goto语句来作为程序流程控制的主要方法。这样做的后果是:一大堆混乱的调转语句使得程序几乎不可能被读懂。对于那个时代的程序员,能读懂上个月自己写的代码都成为一种挑战。

​ 汇编语言仍然应用于工业电子编程领域、软件的加密解密、计算机病毒分析等。

·第三代:高级语言(相当于人类的工业阶段)

对于简单的任务,汇编语言可以胜任。但是随着计算机的发展,渗透到了工作生活的更多的方面,一些复杂的任务出现了,汇编语言就显得力不从心(应该说是程序员使用汇编语言解决复杂问题出现了瓶颈)。于是,出现了高级语言。像我们熟知的C、C++、Java等等都是高级语言。

高级语言允许程序员使用接近日常英语的指令来编写程序。例如,实现一个简单的任务:A+B=C , 使用机器语言、汇编语言和高级语言的的实现。

从上面这个简单的加法计算,可以看出越到高级语言,越接近人的思维,人使用起来就越方便。

高级语言的出现,尤其是面向对象语言的出现,相当于人类的工业社会,高级语言极其易用,编程门槛和难度大大降低,大量的人员进入软件开发行业,为软件爆发性的增长提供了充足的人力资源。目前以及可预见的将来,计算机语言仍然处于“第三代高级语言”阶段。

计算机语言排行榜

1564370752557

计算机语言走势

1561382254180

Java语言概述(了解)

Java生态圈

Java是目前应用最为广泛的软件开发平台之一。随着Java以及Java社区的不断壮大,Java 也早已不再是简简单单的一门计算机语言了,它更是一个平台、一种文化、一个社区。

作为一个平台,Java虚拟机扮演着举足轻重的作用。除了 Java语言,任何一种能够被编译成字节码的计算机语言都属于Java这个平台。Groovy、Scala、 JRuby、Kotlin等都是Java平台的一部分,它们依赖于Java虚拟机,同时,Java平台也因为它们变得更加丰富多彩。

作为一种文化,Java几乎成为了 “开源”的代名词。在Java程序中,有着数不清的开源软件和框架。如Tomcat、Struts, Hibernate, Spring等。就连JDK和JVM自身也有不少开源的实现,如OpenJDK、Apache Harmony。可以说,“共享”的精神在Java世界里体现得淋漓尽致。

作为一个社区,Java拥有全世界最多的技术拥护者和开源社区支持,有数不清的论坛和资料。从桌面应用软件、嵌入式开发到企业级应用、后台服务器、中间件,都可以看到Java的身影。其应用形式之复杂、参与人数之众多也令人咋舌。可以说,Java社区已经俨然成为了一个良好而庞大的生态系统。其实这才是Java最大的优势和财富。

Java 是最好的语言吗?

不是,因为在每个领域都有更合适的编程语言。

  • C 语言无疑是现代计算机软件编程语言的王者,几乎所有的操作系统都是 C 语言写成的。C++ 是面向对象的 C 语言,一直在不断的改进。
  • JavaScript 是能运行在浏览器中的语言,丰富的前端界面离不开 Javascript 的功劳。近年来的 Node.js 又在后端占有一席之地。
  • Python 用于系统管理,并通过高性能预编译的库,提供 API 来进行科学计算,文本处理等,是 Linux 必选的解释性语言。
  • Ruby 强于 DSL(领域特定语言),程序员可以定义丰富的语义来充分表达自己的思想。
  • Erlang 就是为分布式计算设计的,能保证在大规模并发访问的情况下,保持强壮和稳定性。
  • Go 语言内置了并发能力,可以编译成本地代码。当前新的网络相关项目,很大比例是由 Go 语言编写的,如 Docker、Kubernetes 等。
  • 编写网页用 PHP,函数式编程有 Lisp,编写 iOS 程序有 Swift/Objective-C。
  • R的思想是:它可以提供一些集成的统计工具,但更大量的是它提供各种数学计算、统计计算的函数,从而使使用者能灵活机动的进行数据分析,甚至创造出符合需要的新的统计计算方法
  • SQL 是用于访问和处理数据库的标准的计算机语言, 这类数据库包括:MySQL,Oracle, Sybase, SQL Server, DB2, Access 等等

一句话概括,能留在排行榜之上的语言,都是好的语言,在其所在的领域能做到最好。

Java语言发展历史

Java诞生于SUN(Stanford University Network),09年SUN被Oracle(甲骨文)收购。

Java之父是詹姆斯.高斯林(James Gosling)。

1602832163311

1996年发布JDK1.0版。

目前最新的版本是Java13。我们学习的Java8。

发行版本 发行时间 备注
Java 1995.05.23 Sun公司在Sun world会议上正式发布Java和HotJava浏览器
Java 1.0 1996.01.23 Sun公司发布了Java的第一个开发工具包
Java 1.1 1997.02.19
Java 1.2 1998.12.08 拆分成:J2SE(标准版)、J2EE(企业版)、J2ME(小型版)
Java 1.3 2000.05.08
Java1.4 2004.02.06
Java 5.0 2004.09.30 ①版本号从1.4直接更新至5.0;②平台更名为JavaSE、JavaEE、JavaME
Java 6.0 2006.12.11 2009.04.20 Oracle宣布收购SUN公司
Java 7.0 2011.07.02
Java 8.0 2014.03.18
Java 9.0 2017.09.22 ①每半年更新一次;②Java 9.0开始不再支持windows 32位系统
Java 10.0 2018.03.21
Java 11.0 2018.09.25 JDK安装包取消独立JRE安装包
Java 12.0 2019.03.19
Java 13.0 2019.9.18

Java技术体系平台

  • JavaSE(Java Platform, Standard Edition标准版):允许您在桌面和服务器上开发和部署Java应用程序。Java提供了丰富的用户界面、性能、多功能性、可移植性和当今应用程序所需的安全性。
  • JavaEE(Java Platform, Enterprise Edition企业版):是为开发企业环境下的应用程序提供的一套解决方案,主要针对于Web应用程序开发。
  • JavaME(Java Platform, Micro Edition 小型版):为互联网上的嵌入式和移动设备上运行的应用提供了一个健壮、灵活的环境:微控制器、传感器、网关、移动电话、个人数字助理(PDA)、电视机顶盒、打印机等等。JavaME包括灵活的用户界面、健壮的安全性、内置的网络协议,以及支持动态下载的网络和离线应用程序。基于JavaME的应用程序在许多设备上都是可移植的,但是利用了每个设备的本机功能。

    • Java Embedded: 用于解锁物联网的智能设备的价值:
      通过远程市场更新和刷新功能延长产品生命周期和价值;
      利用Java的可伸缩性、健壮性、可移植性和全套功能,提高生产效率,降低成本,缩短上市时间;
      在边缘启用快速数据功能;

    • Java Card:使安全元件(如智能卡和其他防篡改安全芯片)能够承载采用Java技术的应用程序。Java card提供了一个安全的、可互操作的执行平台,它可以在一个资源受限的设备上存储和更新多个应用程序,同时保持最高的认证级别和与标准的兼容性。

    • Java TV:是一种基于JavaME的技术,它为开发在tv和机顶盒设备上运行的java应用程序提供了一个性能良好、安全且易于实现的解决方案。使用Java TV运行时,开发人员可以轻松创建应用程序,例如电子节目指南(EPG)、视频点播(VOD)客户端、游戏和教育应用程序、用于访问Internet数据的应用程序(例如天气、新闻播报器、社交网络)以及大多数蓝光光盘标题上的用户界面和奖金内容。

![1597635498066](https://note-1259153703.cos.ap-nanjing.myqcloud.com/images/20210825230351.png)

Java语言跨平台原理

Java语言的特点

  • 完全面向对象:Java语言支持封装、继承、多态,面向对象编程,让程序更好达到高内聚低耦合的标准。
  • 支持分布式:Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。
  • 健壮型:Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。
  • 安全:Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查。
  • 跨平台性:Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。

Java语言的跨平台原理

  • 跨平台:任何软件的运行,都必须要运行在操作系统之上,而我们用Java编写的软件可以运行在任何的操作系统上,这个特性称为Java语言的跨平台特性。该特性是由JVM实现的,我们编写的程序运行在JVM上,而JVM运行在操作系统上。
  • JVM(Java Virtual Machine ):Java虚拟机,简称JVM,是运行所有Java程序的假想计算机,是Java程序的运行环境之一,也是Java 最具吸引力的特性之一。我们编写的Java代码,都运行在JVM 之上。

1602837063456

如图所示,Java的虚拟机本身是不具备跨平台功能的,每个操作系统下都有不同版本的虚拟机。

  • JRE (Java Runtime Environment) :是Java程序的运行时环境,包含JVM 和运行时所需要的核心类库
  • JDK (Java Development Kit):是Java程序开发工具包,包含JRE 和开发人员使用的工具。

我们想要运行一个已有的Java程序,那么只需安装JRE 即可。

我们想要开发一个全新的Java程序,那么必须安装JDK ,其内部包含JRE

1561383524152

JDK下载和安装

JDK的下载

  • 下载网址:www.oracle.com

  • 下载步骤:

    • 登录Oracle公司官网,www.oracle.com,如图所示:在底部选择Developers开发者

      1572254490435

    • Developers页面中间的技术分类部分,选择Java,单击进入,如图所示:

      1572309624793

    • 下拉页面,找到Java,在此选择JavaSEDownload,单击进入,如图所示:

    1572309711034

    • 选择Downloads选项卡,默认是最新版的Java13下载,在此处选择Oracle JDK DOWNLOAD,单击进入可以下载JDK13,如图所示:

1572309841433

选择Accept License Agreement,并选择对应的操作系统类型,如图所示

1572310014408

  • 如果要下载之前JDK版本,那么在刚才JavaSE/Download页面,下拉到最下面,找到Java Archive(Java档案馆),单击Download

1572310151403

例如:这里选择JavaSE 8(8U211 and later),选择Accept License Agreement,并选择对应的操作系统类型。早期版本分为32位/64位操作系统区分,其中x86表示32位,x64表示64位。

1572310481782

JDK的安装

  • 安装步骤:

    • 双击jdk-8u202-windows-x64.exe文件,并单击下一步,如图所示:

    • 取消独立JRE的安装,单击公共JRE前的下拉列表,选择此功能将不可用如图所示:

    • 修改安装路径,单击更改,如图所示:

    • 将安装路径修改为D:\develop\Java\jdk1.8.0_202\,并单击确定,如图所示:

    • 单击下一步,如图所示:

    • 稍后几秒,安装完成,如图所示:

    • 目录结构,如图所示:

      1561386792819

常用DOS命令(了解)

什么是DOS

Java语言的初学者,学习一些DOS命令,会非常有帮助。DOS是一个早期的操作系统,现在已经被Windows系统取代,对于我们开发人员,目前需要在DOS中完成一些事情,因此就需要掌握一些必要的命令。

Dos: Disk Operating System 磁盘操作系统, 简单说一下windows的目录结构。

image-20200213095127784

进入DOS操作窗口

  • 按下Windows+R键,打开运行窗口,输入cmd回车,进入到DOS的操作窗口。

    运行窗口

  • 打开DOS命令行后,看到一个路径 C:\Users\... 就表示我们现在操作的磁盘是C盘的Users的final目录。

image-20200213083838587

常用命令

进入目录命令:cd

(1)回到根目录

cd /  或  cd \

image-20200213103050182

(2)切换到上一级

cd ..

image-20200213102356751

(3)当前盘的其他目录下

绝对路径:从根目录开始定位,例如:cd d:\test200\1 或者 cd d:/test200/1

相对路径:从当前目录开始定位,例如:..\..\..\\test200\\1 或者 ../../../test200/1

例如:现在在d:/test100/hello/a目录,要切换到d:/test200/1目录

image-20200213101458688

image-20200213101428222

image-20200213101246431

切换盘符命令

(1)直接盘符:

例如:要切换到D盘,直接d:

(2)使用cd命令

例如:要切换到E盘,可以使用cd /D e:

使用 /D 开关,除了改变驱动器的当前目录之外,还可改变当前驱动器。

image-20200213101745482

查看当前目录下有什么命令:dir

image-20200213101952912

image-20200213102043229

新建目录命令:md (make directory)

//在当前目录下创建hello文件夹
md hello

//在当前目录下创建a,b,c三个文件夹
md a b c

//在d盘test200下创建ok200文件夹
md d:\test200\ok200

image-20200213103500670

删除文件命令:del

//删除指定文件
del 文件名.扩展名
del 目标目录\文件名.扩展名

删除所有文件并询问
del *.*

删除所有文件不询问
del /Q *.*

image-20200213104335172

image-20200213104501033

image-20200213104938765

删除目录命令:rd(remove directory)

//删除空目录
rd 空目录名

//删除目录以及下面的子目录和文件,带询问
rd /S 非空目录名

//删除目录以及下面的子目录和文件,不带询问
rd /S/Q 非空目录名

注意:你在d:\test100\hello\a中,你不能删除test100、hello、a这几个目录

image-20200213105002765

image-20200213105012075

清屏命令:cls

cls

退出命令:exit

exit

配置环境变量

为什么配置path?

希望在命令行使用javac.exe等工具时,任意目录下都可以找到这个工具所在的目录。

例如:我们在C:\Users\Irene目录下使用java命令,结果如下:

1572317249341

我们在JDK的安装目录的bin目录下使用java命令,结果如下:

1572317330332

我们不可能每次使用java.exe,javac.exe等工具的时候都进入到JDK的安装目录下,太麻烦了。我们希望在任意目录下都可以使用JDK的bin目录的开发工具,因此我们需要告诉操作系统去哪里找这些开发工具,这就需要配置path环境变量。

只配置path

  • 步骤:

    • 打开桌面上的计算机,进入后在左侧找到计算机,单击鼠标右键,选择属性,如图所示:

    • 选择高级系统设置,如图所示:

    • 高级选项卡,单击环境变量,如图所示:

    • 系统变量中,选中Path 环境变量,双击或者点击编辑 ,如图所示:

    • 在变量值的最前面,键入D:\develop\Java\jdk1.8.0_202\bin; 分号必须要写,而且还要是英文符号。如图所示:

      1561386643207

    • 环境变量配置完成,重新开启DOS命令行,在任意目录下输入javac 命令,运行成功。

配置JAVA_HOME+path

  • 步骤:

    • 打开桌面上的计算机,进入后在左侧找到计算机,单击鼠标右键,选择属性,如图所示:

    • 选择高级系统设置,如图所示:

    • 高级选项卡,单击环境变量,如图所示:

    • 系统变量中,单击新建 ,创建新的环境变量,如图所示:

    • 变量名输入JAVA_HOME,变量值输入 D:\develop\Java\jdk1.8.0_202 ,并单击确定,如图所示:

    • 选中Path 环境变量,双击或者点击编辑 ,如图所示:

    • 在变量值的最前面,键入%JAVA_HOME%\bin; 分号必须要写,而且还要是英文符号。如图所示:

    • 环境变量配置完成,重新开启DOS命令行,在任意目录下输入javac 命令,运行成功。

入门程序HelloWorld

HelloWorld案例

程序开发步骤说明

JDK安装完毕,可以开发我们第一个Java程序了。

Java程序开发三步骤:编写编译运行

开发步骤

编写Java源程序

  1. D:\atguigu\day01_code 目录下新建文本文件,完整的文件名修改为HelloWorld.java,其中文件名为HelloWorld,后缀名必须为.java
  2. 用记事本或notepad++等文本编辑器打开

  3. 在文件中键入文本并保存,代码如下:

public class HelloWorld {
  	public static void main(String[] args) {
    	System.out.println("HelloWorld");
  	}
}

友情提示:

每个字母和符号必须与示例代码一模一样。

第一个HelloWord 源程序就编写完成了,但是这个文件是程序员编写的,JVM是看不懂的,也就不能运行,因此我们必须将编写好的Java源文件 编译成JVM可以看懂的字节码文件 ,也就是.class文件。

编译Java源文件

在DOS命令行中,进入D:\atguigu\javaee\JavaSE20190624\code\day01_code目录,使用javac 命令进行编译。

命令:

javac Java源文件名.后缀名

举例:

javac HelloWorld.java

1561387081272

编译成功后,命令行没有任何提示。打开D:\atguigu\javaee\JavaSE20190624\code\day01_code目录,发现产生了一个新的文件 HelloWorld.class,该文件就是编译后的文件,是Java的可运行文件,称为字节码文件,有了字节码文件,就可以运行程序了。

Java源文件的编译工具javac.exe

运行Java程序

在DOS命令行中,进入Java源文件的目录,使用java 命令进行运行。

命令:

java 类名字

举例:

java HelloWorld

友情提示:

java HelloWord 不要写 不要写 不要写 .class

1561387134284

Java字节码文件的运行工具:java.exe

HelloWorld案例常见错误

  • 单词拼写问题
    • 正确:class 错误:Class
    • 正确:String 错误:string
    • 正确:System 错误:system
    • 正确:main 错误:mian
  • Java语言是一门严格区分大小写的语言
  • 标点符号使用问题
    • 不能用中文符号,英文半角的标点符号(正确)
    • 括号问题,成对出现

Java程序的结构与格式

结构:

{
    方法{
        语句;
    }
}

格式:

(1)每一级缩进一个Tab键

(2){}的左半部分在行尾,右半部分单独一行,与和它成对的”{“的行首对齐

Java程序的入口

Java程序的入口是main方法

public static void main(String[] args){
    
}

编写Java程序时应该注意的问题

1、字符编码问题

当cmd命令行窗口的字符编码与.java源文件的字符编码不一致,如何解决?

1557881223916

解决方案一:

在Notepad++等编辑器中,修改源文件的字符编码

1557881271819

解决方案二:

在使用javac命令式,可以指定源文件的字符编码
javac -encoding utf-8 Review01.java

2、大小写问题

(1)源文件名:

不区分大小写,我们建议大家还是区分

(2)字节码文件名与类名

区分大小写

(3)代码中

区分大小写

3、源文件名与类名一致问题?

(1)源文件名是否必须与类名一致?public呢?

如果这个类不是public,那么源文件名可以和类名不一致。

如果这个类是public,那么要求源文件名必须与类名一致。

我们建议大家,不管是否是public,都与源文件名保持一致,而且一个源文件尽量只写一个类,目的是为了好维护。

(2)一个源文件中是否可以有多个类?public呢?

一个源文件中可以有多个类,编译后会生成多个.class字节码文件。

但是一个源文件只能有一个public的类。

(3)main必须在public的类中吗?

不是。

但是后面写代码时,基本上main习惯上都在public类中。

Java基础知识

注释(annotation

  • 注释:就是对代码的解释和说明。其目的是让人们能够更加轻松地了解代码。为代码添加注释,是十分必须要的,它不影响程序的编译和运行。
  • Java中有单行注释多行注释文档注释

    • 单行注释以 //开头,以换行结束,格式如下:

      // 注释内容
    • 多行注释以 /*开头,以*/结束,格式如下:

      /*
      	注释内容
       */
    • 文档注释以/**开头,以*/结束

      /**
      	注释内容
       */

关键字(keyword

关键字:是指在程序中,Java已经定义好的单词,具有特殊含义。

  • HelloWorld案例中,出现的关键字有 publicclassstaticvoid 等,这些单词已经被Java定义好
  • 关键字的特点:全部都是小写字母
  • 关键字比较多,不需要死记硬背,学到哪里记到哪里即可。

1555209180504

关键字一共50个,其中const和goto是保留字。

true,false,null看起来像关键字,但从技术角度,它们是特殊的布尔值和空值。

标识符( identifier)

简单的说,凡是程序员自己命名的部分都可以称为标识符。

即给类、变量、方法、包等命名的字符序列,称为标识符。

1、标识符的命名规则(必须遵守)

(1)Java的标识符只能使用26个英文字母大小写,0-9的数字,下划线_,美元符号$

(2)不能使用Java的关键字(包含保留字)和特殊值

(3)数字不能开头

(4)不能包含空格

(5)严格区分大小写

2、标识符的命名规范(遭受鄙视)

(1)见名知意

(2)类名、接口名等:每个单词的首字母都大写,形式:XxxYyyZzz,

例如:HelloWorld,String,System等

(3)变量、方法名等:从第二个单词开始首字母大写,其余字母小写,形式:xxxYyyZzz,

例如:age,name,bookName,main

(4)包名等:每一个单词都小写,单词之间使用点.分割,形式:xxx.yyy.zzz,

例如:java.lang

(5)常量名等:每一个单词都大写,单词之间使用下划线_分割,形式:XXX_YYY_ZZZ,

例如:MAX_VALUE,PI

初识数据类型(data type)

数据类型分类

Java的数据类型分为两大类:

  • 基本数据类型:包括 整数浮点数字符布尔
  • 引用数据类型:包括 数组接口

基本数据类型

四类八种基本数据类型:

Java中的默认类型:整数类型是int 、浮点类型是double

常量(constant

  • 常量:在程序执行的过程中,其值不可以发生改变的量

  • 常量的分类:

    • 自定义常量:通过final关键字定义(后面在面向对象部分讲解)

    • 字面值常量:

      | 字面值常量 | 举例 |
      | :————: | :——————: |
      | 字符串常量 | ”HelloWorld“ |
      | 整数常量 | 12,-23 |
      | 浮点常量 | 12.34 |
      | 字符常量 | ‘a’,’0’,‘我’ |
      | 布尔常量 | true,false |
      | 空常量 | null |

      public class ConstantDemo {
      	public static void main(String[] args) {
      		//字符串常量
      		System.out.println("HelloWorld");
      		
      		//整数常量
      		System.out.println(12);
      		System.out.println(-23);
      		
      		//小数常量
      		System.out.println(12.34);
      		
      		//字符常量
      		System.out.println('a');
      		System.out.println('0');
              System.out.println('我');
      		
      		//布尔常量
      		System.out.println(true);
      		System.out.println(false);
      	}
      }

      注意事项:

      ​ 字符常量,单引号里面有且仅有一个字符

      ​ 空常量,不可以在输出语句中直接打印

变量(variable

变量的概念

变量:在程序执行的过程中,其值可以发生改变的量

变量的作用:用来存储数据,代表内存的一块存储区域,这块内存中的值是可以改变的。

变量的三要素

1、数据类型

2、变量名

3、值

变量的使用应该注意什么?

1、先声明后使用

如果没有声明,会报“找不到符号”错误

2、在使用之前必须初始化

如果没有初始化,会报“未初始化”错误

3、变量有作用域

如果超过作用域,也会报“找不到符号”错误

4、在同一个作用域中不能重名

变量的声明和赋值、使用的语法格式?

1、变量的声明的语法格式:

数据类型  变量名;
例如:
int age;
String name;
double weight;
char gender;
boolean isMarry;

2、变量的赋值的语法格式:

变量名 =;
例如:
age = 18;
name = "古力娜扎"; //字符串的值必须用""
weight = 44.4;
gender = '女';//单字符的值必须使用''
isMarry = true;

3、变量的使用的语法格式:

通过变量名直接引用

例如:
(1)输出变量的值
System.out.println(age);
System.out.println(name);
System.out.println(weight);
System.out.println(gender);
System.out.println(isMarry);

(2)计算
age = age + 1;//年龄增加1岁

练习:定义所有基本数据类型的变量和字符串变量并输出

public class VariableDemo {
	public static void main(String[] args){
        // 定义字节型变量
        byte b = 100;
        System.out.println(b);
        // 定义短整型变量
        short s = 1000;
        System.out.println(s);
        // 定义整型变量
        int i = 123456;
        System.out.println(i);
        // 定义长整型变量
        long l = 12345678900L;
        System.out.println(l);
        // 定义单精度浮点型变量
        float f = 5.5F;
        System.out.println(f);
        // 定义双精度浮点型变量
        double d = 8.5;
        System.out.println(d);
        // 定义布尔型变量
        boolean bool = false;
        System.out.println(bool);
        // 定义字符型变量
        char c = 'A';
        System.out.println(c);
        
        // 定义字符串变量
        String s = "HelloWorld";
        System.out.println(s);
	}
}

long类型:如果赋值的常量整数超过int范围,那么需要在数字后面加L。

float类型:如果赋值为常量小数,那么需要在小数后面加F。

char类型:使用单引号’’

String类型:使用双引号””

两种常见的输出语句

  • 换行输出语句:输出内容,完毕后进行换行,格式如下:

    System.out.println(输出内容);
  • 直接输出语句:输出内容,完毕后不做任何处理,格式如下

    System.out.print(输出内容);

示例代码:

String name = "古力娜扎";
int age = 18;

对比如下两组代码:
System.out.println(name);
System.out.println(age);

System.out.print(name);
System.out.print(age);

对比如下两组代码:
System.out.print("姓名:" + name +",");//""中的内容会原样显示
System.out.println("年龄:" + age);//""中的内容会原样显示

System.out.print("name = " + name + ",");
System.out.println("age = " + age);

>

注意事项:

​ 换行输出语句,括号内可以什么都不写,只做换行处理

​ 直接输出语句,括号内什么都不写的话,编译报错

​ 如果()中有多项内容,那么必须使用 + 连接起来

​ 如果某些内容想要原样输出,就用””引起来,而要输出变量中的内容,则不要把变量名用””引起来

计算机如何存储数据

计算机世界中只有二进制。那么在计算机中存储和运算的所有数据都要转为二进制。包括数字、字符、图片、声音、视频等。

img

进制(了解)

进制也就是进位计数制,是人为定义的带进位的计数方法 。

进制的分类

(1)十进制:
数字组成:0-9
进位规则:逢十进一

(2)二进制:
数字组成:0-1
进位规则:逢二进一

十进制的256,二进制:100000000,为了缩短二进制的表示,又要贴近二进制,在程序中引入八进制和十六进制

(3)八进制:很少使用
数字组成:0-7
进位规则:逢八进一

与二进制换算规则:每三位二进制是一位八进制值

(4)十六进制
数字组成:0-9,a-f
进位规则:逢十六进一

与二进制换算规则:每四位二进制是一位十六进制值

进制的换算

十进制 二进制 八进制 十六进制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 a或A
11 1011 13 b或B
12 1100 14 c或C
13 1101 15 d或D
14 1110 16 e或E
15 1111 17 f或F
16 10000 20 10
  • 十进制数据转成二进制数据:使用除以2倒取余数的方式

  • 二进制数据转成十进制数据:

    从右边开始依次是2的0次,2的1次,2的2次。。。。

  • 二进制数据转八进制数据

    ​ 从右边开始,三位一组

  • 二进制数据转十六进制数据

    ​ 从右边开始,四位一组

在代码中如何表示四种进制的值

请分别用四种类型的进制来表示10,并输出它的结果:(了解)

(1)十进制:正常表示

System.out.println(10);

(2)二进制:0b或0B开头

System.out.println(0B10);

(3)八进制:0开头

System.out.println(010);

(4)十六进制:0x或0X开头

System.out.println(0X10);

计算机存储单位

  • 字节(Byte):是计算机信息技术用于计量存储容量的一种计量单位,一字节等于八位。

  • 位(bit):是数据存储的最小单位。也就是二进制。二进制数系统中,每个0或1就是一个位,叫做bit(比特),其中8 bit 就称为一个字节(Byte)。

  • 转换关系:

    • 8 bit = 1 Byte
    • 1024 Byte = 1 KB
    • 1024 KB = 1 MB
    • 1024 MB = 1 GB
    • 1024 GB = 1 TB

Java的基本数据类型的存储范围

整型系列

(1)byte:字节类型

  • 占内存:1个字节

  • 存储范围:-128~127

(2)short:短整型类型

  • 占内存:2个字节

  • 存储范围:-32768~32767

(3)int:整型

  • 占内存:4个字节

  • 存储范围:-2的31次方 ~ 2的31次方-1

(4)long:整型

  • 占内存:8个字节

  • 存储范围:-2的63次方 ~ 2的63次方-1

注意:如果要表示某个超过int范围的常量整数它是long类型,那么需要在数字后面加L

浮点型系列(小数)

(1)float:单精度浮点型

  • 占内存:4个字节

  • 精度:科学记数法的小数点后6~7位

注意:如果要表示某个常量小数是float类型,那么需要在数字后面加F或f,否则就是double类型

(2)double:双精度浮点型

  • 占内存:8个字节

  • 精度:科学记数法的小数点后15~16位

float f = 12.0F;//右边如果赋值小数常量值,那么必须加F或f

单字符类型:char

  • 占内存:2个字节

布尔类型

boolean:只能存储true或false

虽然计算机底层使用0和1表示false和true,但是在代码中不能给boolean类型的变量赋值0和1,只能赋值false和true

计算机如何存储数据

补码与符号位

计算机数据的存储使用二进制补码形式存储,并且最高位是符号位,1是负数,0是正数。

规定:正数的补码与反码、原码一样,称为三码合一;

负数的补码与反码、原码不一样:

负数的原码:把十进制转为二进制,然后最高位设置为1

负数的反码:在原码的基础上,最高位不变,其余位取反(0变1,1变0)

负数的补码:反码+1

例如:byte类型(1个字节,8位)

25 ==> 原码 0001 1001 ==> 反码 0001 1001 —>补码 0001 1001

-25 ==>原码 1001 1001 ==> 反码1110 0110 ==>补码 1110 0111

底层是用加法代替减法:-128==》-127-1==》-127+(-1)

​ -127- -1 ==> -127 + 1

一个字节可以存储的数据范围是多少?

(1)无符号:不考虑正负数

(2)有符号

1个字节:8位

0000 0001 ~ 0111 111 ==> 1~127

1000 0001 ~ 1111 1111 ==> -127 ~ -1

0000 0000 ==>0

1000 0000 ==> -128(特殊规定)

如何存储小数

  • 为什么float(4个字节)比long(8个字节)的存储范围大?

  • 为什么double(8个字节)比float(4个字节)精度范围大?

  • 为什么float和double不精确

因为float、double底层也是二进制,先把小数转为二进制,然后把二进制表示为科学记数法,然后只保存:

①符号位②指数位③尾数位

详见《float型和double型数据的存储方式.docx》

如何存储字符

  • Java中使用的字符集:Unicode字符集
编码表

在计算机的内部都是二进制的0、1数据,如何让计算机可以直接识别人类文字的问题呢?就产生出了编码表的概念。编码表 :就是将人类的文字和一个十进制数进行对应起来组成一张表格。例如:

字符 数值
0 48
A 65
a 97

将所有的英文字母,数字,符号都和十进制进行了对应,因此产生了世界上第一张编码表ASCII(American Standard Code for Information Interchange 美国标准信息交换码)。

Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

字符常量的几种表示方式

(1)’一个字符’

例如:’A’,’0’,’尚’

(2)转义字符

\n:换行
\r:回车
\t:Tab键
\\:\
\":"
\':'
\b:删除键Backspace

System.out.println('\\');
System.out.println("hello\tworld\njava");

(3)\u字符的Unicode编码值的十六进制型

例如:’\u5c1a’代表’尚’

char c = '\u5c1a';
char c = '尚';
String s = '尚';//错误的,哪怕是一个字符,也要使用双引号

char c2 = '';//错误,单引号中有且只能有一个字符
String s2 = "";//可以,双引号中可以没有其他字符,表示是空字符串

(4)直接给char类型变量赋值十进制的0~65535之间的Unicode编码值

例如:’尚’ 的编码值是23578

​ ‘a’的编码值是97

char c1 = 23578;
System.out.println(c1);//尚

char c2 = 97;
System.out.println(c2);//a

基本数据类型转换(Conversion)

在Java程序中,不同的基本数据类型的值经常需要进行相互转换。Java语言所提供的七种数值类型之间可以相互转换,基本数据类型转换有两种转换方式:自动类型转换和强制类型转换。

自动类型转换(隐式类型转换)

自动转换

  • 取值范围小的类型自动提升为取值范围大的类型

基本数据类型的转换规则

小结:通过上面案例我们可以得出数据类型的转换关系(取值范围从小到大),如图所示:

(1)当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时,

int i = 'A';//char自动升级为int
double d = 10;//int自动升级为double

byte b = 127; //右边的整数常量值必须在-128~127范围内
//byte bigB = 130;//错误,右边的整数常量值超过byte范围
long num = 1234567; //右边的整数常量值如果在int范围呢,编译和运行都可以通过,这里涉及到数据类型转换
long bigNum = 12345678912L;//右边的整数常量值如果超过int范围,必须加L,否则编译不通过

(2)当存储范围小的数据类型与存储范围大的数据类型一起混合运算时,会按照其中最大的类型运算

int i = 1;
byte b = 1;
double d = 1.0;

double sum = i + b + d;//混合运算,升级为double

(3)当byte,short,char数据类型进行算术运算时,按照int类型处理

byte b1 = 1;
byte b2 = 2;
byte b3 = b1 + b2;//编译报错,b1 + b2自动升级为int

char c1 = '0';
char c2 = 'A';
System.out.println(c1 + c2);//113 

(4)boolean类型不参与

强制类型转换(显示类型转换)

1.5 赋值到int 类型变量会发生什么?产生编译失败,肯定无法赋值。

int i = 3.14; // 错误

想要赋值成功,只有通过强制类型转换,将double 类型强制转换成int 类型才能赋值。

  • 强制类型转换:将取值范围大的类型强制转换成取值范围小的类型

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

转换格式:

数据类型 变量名 = (数据类型)被强转数据值;

(1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围小的变量时,需要强制类型转换,提示:有风险,可能会损失精度或溢出

int i = (int)3.14;//强制类型转换,损失精度

double d = 1.2;
int num = (int)d;//损失精度

int i = 200;
byte b = (byte)i;//溢出

(2)boolean类型不参与

(3)当某个值想要提升数据类型时,也可以使用强制类型转换

int i = 1;
int j = 2;
double shang = (double)i/j;

提示:这个情况的强制类型转换是没有风险的。

特殊的数据类型转换

1、任意数据类型的数据与String类型进行“+”运算时,结果一定是String类型

System.out.println("" + 1 + 2);//12

2、但是String类型不能通过强制类型()转换,转为其他的类型

String str = "123";
int num = (int)str;//错误的
int num = Integer.parseInt(str);//后面才能讲到,借助包装类的方法才能转

练习

1、练习题:判断如下代码是否编译通过,如果能,结果是多少?
short s1 = 12;
short s2 = 8;
short s3 = s1 + s2;

2、练习题:判断如下代码是否编译通过,如果能,结果是多少?
short s1 = 12;
short s2 = 8;
byte s3 = (byte)(s1 + s2);

3、练习题:判断如下代码是否编译通过,如果能,结果是多少?
char c1 = '0';
char c2 = '1';
char c3 = c1 + c2;

4、练习题:判断如下代码是否编译通过,如果能,结果是多少?
char c1 = '0';
char c2 = '1';
System.out.println(c1 + c2);

5、练习题:判断如下代码是否编译通过,如果能,结果是多少?
int i = 4;
long j = 120; //因为右边120默认是int类型,int的值赋值给long类型是可以的,会自动类型转换,但是要求这个int值不能超过int的存储范围,如果超过int的存储范围必须加L.
double d = 34;
float f = 1.2;//因为右边1.2默认是double类型,double的值是不能直接赋值给float的,要么加F要么使用强制类型转换。

System.out.println(i + j + d + f);//最后是double

6、练习题:判断如下代码是否编译通过,如果能,结果是多少?
int i = 1;
int j = 2;
double d = i/j;
System.out.println(d);

运算符(Operator)

  • 表达式:用运算符连接起来的式子
  • 运算符的分类:
    按照功能分:算术运算符、赋值运算符、比较运算符、逻辑运算、条件运算符…
分类 运算符
算术运算符 +-*/%++--
赋值运算符 =+=-=*=/=%=
关系运算符 >>=<<===!=
逻辑运算符 &、` ^!&& `
条件运算符 (条件表达式)?结果1:结果2;
位运算符(了解) &、` ~^<<>>>>>`
  • 按照操作数个数分:一元运算符(单目运算符)、二元运算符(双目运算符)、三元运算符 (三目运算符)

    一元运算符:操作数只有一个

      例如:正号(+)  +a
            负号(-)  -a
            自增自减   ++i  i++
            逻辑非:   !true
    

    二元运算符:操作数有两个

      例如:加法:a+b
            减法:a-b
            大于:a>b
            逻辑与:a&b
    

    三元运算符:条件 ? 结果1 : 结果2

算术运算符

算术运算符 符号解释
+ 加法运算,字符串连接运算,正号
- 减法运算,负号
* 乘法运算
/ 除法运算,整数/整数结果还是整数
% 求余运算,余数的符号只看被除数
++-- 自增自减运算

加减乘除模

public class OperatorDemo01 {
	public static void main(String[] args) {
		int a = 3;
		int b = 4;
		
		System.out.println(a + b);// 7
		System.out.println(a - b);// -1
		System.out.println(a * b);// 12
		System.out.println(a / b);// 计算机结果是0,为什么不是0.75呢?
		System.out.println(a % b);// 3
        
        System.out.println(5%2);//1
		System.out.println(5%-2);//1
		System.out.println(-5%2);//-1
		System.out.println(-5%-2);//-1		
		//商*除数 + 余数 = 被除数
		//5%-2  ==>商是-2,余数时1    (-2)*(-2)+1 = 5
		//-5%2  ==>商是-2,余数是-1   (-2)*2+(-1) = -4-1=-5
	}
}

“+”号的两种用法

  • 第一种:对于+两边都是数值的话,+就是加法的意思
  • 第二种:对于+两边至少有一边是字符串得话,+就是拼接的意思
public class OperatorDemo02 {
	public static void main(String[] args) {
		// 字符串类型的变量基本使用
		// 数据类型 变量名称 = 数据值;
		String str1 = "Hello";
		System.out.println(str1); // Hello
		
		System.out.println("Hello" + "World"); // HelloWorld
		
		String str2 = "Java";
		// String + int --> String
		System.out.println(str2 + 520); // Java520
		// String + int + int
		// String		+ int
		// String
		System.out.println(str2 + 5 + 20); // Java520
	}
}

自加自减运算

理解:++ 运算,变量自己的值加1。反之,-- 运算,变量自己的值减少1,用法与++ 一致。

1、单独使用

  • 变量在单独运算的时候,变量前++和变量后++,变量的是一样的;
  • 变量前++ :例如 ++a
  • 变量后++ :例如 a++
public class OperatorDemo3 {
	public static void main(String[] args) {
		// 定义一个int类型的变量a
		int a = 3;
		//++a;
		a++;
        // 无论是变量前++还是变量后++,结果都是4
		System.out.println(a);
	}
}

2、复合使用

  • 其他变量放在一起使用或者和输出语句放在一起使用前++后++就产生了不同。
  • 变量前++ :变量先自身加1,然后再取值。
  • 变量后++ :变量先取值,然后再自身加1。
public class OperatorDemo03 {
	public static void main(String[] args) {
		// 其他变量放在一起使用
		int x = 3;
		//int y = ++x; // y的值是4,x的值是4,
		int y = x++; // y的值是3,x的值是4
		
		System.out.println(x);
		System.out.println(y);
		System.out.println("==========");
        
		// 和输出语句一起
		int z = 5;
		//System.out.println(++z);// 输出结果是6,z的值也是6
		System.out.println(z++);// 输出结果是5,z的值是6
		System.out.println(z);
        
        int a = 1;
        a = a++;//(1)先取a的值“1”放操作数栈(2)a再自增,a=2(3)再把操作数栈中的"1"赋值给a,a=1

        int i = 1;
        int j = i++ + ++i * i++;
        /*
        从左往右加载
        (1)先算i++
        ①取i的值“1”放操作数栈
        ②i再自增 i=2
        (2)再算++i
        ①i先自增 i=3
        ②再取i的值“3”放操作数栈
        (3)再算i++
        ①取i的值“3”放操作数栈
        ②i再自增 i=4
        (4)先算乘法
        用操作数栈中3 * 3 = 9,并把9压会操作数栈
        (5)再算求和
        用操作数栈中的 1 + 9 = 10
        (6)最后算赋值
        j = 10
        */
	} 
}
  • 小结:
    • ++在前,先自加,后使用;
    • ++在后,先使用,后自加。

练习

获取一个四位数的个位,十位,百位,千位
public class Test01 {
	public static void main (String [] args) {
		//1.定义一个四位数,例如1234
		int num = 1234;
        
		//2.通过运算操作求出个位,十位,百位,千位
		int ge =int shi =int bai =int qian =System.out.println(num + "这个四位数个位上的数字是:" + ge);
		System.out.println(num + "这个四位数十位上的数字是:" + shi);
		System.out.println(num + "这个四位数百位上的数字是:" + bai);
		System.out.println(num + "这个四位数千位上的数字是:" + qian);
	}
}
自增自减练习

判断如下代码的运行结果

public static void main(String[] args){
	int i = 1;
	int j = i++;
	int k = i++ * ++j + ++i * j++;
	
	System.out.println("i = " + i);
	System.out.println("j = " + j);
	System.out.println("k = " + k);
}
public static void main(String[] args){
	int i = 1;
	int j = i++;
	int k = i++ * ++j + --i * j--;
	
	System.out.println("i = " + i);
	System.out.println("j = " + j);
	System.out.println("k = " + k);
}
public static void main(String[] args){
	int i = 1;
	int j = ++i + i++ * ++i + i++;
	
	System.out.println("i = " + i);
	System.out.println("j = " + j);
}
public static void main(String[] args){
	int i = 0;
	int result = ++i/--i;
	System.out.println("result="+result);
}

赋值运算符

注意:所有的赋值运算符的=左边一定是一个变量

赋值运算符 符号解释
= 将符号右边的值,赋值给左边的变量
+= 将符号左边的值右边的值进行相加操作,最后将结果赋值给左边的变量
-= 将符号左边的值右边的值进行相减操作,最后将结果赋值给左边的变量
*= 将符号左边的值右边的值进行相乘操作,最后将结果赋值给左边的变量
/= 将符号左边的值右边的值进行相除操作,最后将结果赋值给左边的变量
%= 将符号左边的值右边的值进行取余操作,最后将结果赋值给左边的变量

基本赋值运算符课堂案例

public class OperatorDemo04 {
	public static void main(String[] args) {
		int a = 3;
		int b = 4;
		a = a + b; 
		System.out.println(a); // 7
		System.out.println(b); // 4	
	}
}

扩展赋值运算符课堂案例

public class OperatorDemo04 {
	public static void main(String[] args) {
		int a = 3;
		int b = 4;
		b += a;// 相当于 b = b + a ; 
		System.out.println(a); // 3
		System.out.println(b); // 7	
		
		short s = 3;
		// s = s + 4; 代码编译报错,因为将int类型的结果赋值给short类型的变量s时,可能损失精度
		s += 4; // 代码没有报错
        //因为在得到int类型的结果后,JVM自动完成一步强制类型转换,将int类型强转成short
		System.out.println(s);
        
        int j = 1;
		j += ++j * j++;//相当于  j = j + (++j * j++);
		System.out.println(j);//5
	}
}
  • 扩展赋值运算符在将最后的结果赋值给左边的变量前,都做了一步强制类型转换

练习

交换两个变量的值
int m = 1;
int n = 2;

int m = 1;
int n = 2;
int temp = m;
m = n;
n = temp;

关系运算符/比较运算符

关系运算符 符号解释
< 比较符号左边的数据是否小于右边的数据,如果小于结果是true。
> 比较符号左边的数据是否大于右边的数据,如果大于结果是true。
<= 比较符号左边的数据是否小于或者等于右边的数据,如果大于结果是false。
>= 比较符号左边的数据是否大于或者等于右边的数据,如果小于结果是false。
== 比较符号两边数据是否相等,相等结果是true。
!= 不等于符号 ,如果符号两边的数据不相等,结果是true。
  • 比较运算符,是两个数据之间进行比较的运算,运算结果一定是boolean值true或者false
  • 其中>,<,>=,<=不支持boolean,String类型,==和!=支持boolean和String。
public class OperatorDemo05 {
	public static void main(String[] args) {
		int a = 3;
		int b = 4;

		System.out.println(a < b); // true
		System.out.println(a > b); // false
		System.out.println(a <= b); // true
		System.out.println(a >= b); // false
		System.out.println(a == b); // false
		System.out.println(a != b); // true
	}
}

练习:判断如下程序的运行结果

public static void main(String[] args){
	int a = 1;
	int b = 2;
	int c = 0;
	boolean flag = false;
	if(flag=true){
		c = a++ + b;
	}

	if(flag=false){
		c = ++a - b;
	}
	System.out.println("a = " + a);
	System.out.println("b = " + b);
	System.out.println("c = " + c);
}	

逻辑运算符

  • 逻辑运算符,是用来连接两个布尔类型结果的运算符(!除外),运算结果一定是boolean值true或者false
逻辑运算符 符号解释 符号特点
& 与,且 falsefalse
` ` truetrue
^ 异或 相同为false,不同为true
! falsetrue,非truefalse
&& 双与,短路与 左边为false,则右边就不看
` ` 双或,短路或 左边为true,则右边就不看

 课堂案例

public class OperatorDemo06 {
	public static void main(String[] args) {
		int a = 3;
		int b = 4;
		int c = 5;

		// & 与,且;有false则false
		System.out.println((a > b) & (a > c)); 
		System.out.println((a > b) & (a < c)); 
		System.out.println((a < b) & (a > c)); 
		System.out.println((a < b) & (a < c)); 
		System.out.println("===============");
		// | 或;有true则true
		System.out.println((a > b) | (a > c)); 
		System.out.println((a > b) | (a < c)); 
		System.out.println((a < b) | (a > c));
		System.out.println((a < b) | (a < c));
		System.out.println("===============");
		// ^ 异或;相同为false,不同为true
		System.out.println((a > b) ^ (a > c));
		System.out.println((a > b) ^ (a < c)); 
		System.out.println((a < b) ^ (a > c)); 
		System.out.println((a < b) ^ (a < c)); 
		System.out.println("===============");
		// ! 非;非false则true,非true则false
		System.out.println(!false);
		System.out.println(!true);
	}
}

  &&和&区别,||和|区别

  • &&&区别:
    • &&&结果一样,&&有短路效果,左边为false,右边不执行;&左边无论是什么,右边都会执行。
  • |||区别:
    • |||结果一样,||有短路效果,左边为true,右边不执行;|左边无论是什么,右边都会执行。

面试题1

1561431178935

public class LogicExer1{
	public static void main(String[] args){
		int x = 1;
		int y = 1;

		//x==2 ,x++  false  x = 2 左边为false
		//右边继续
		//++y  y==2  y=2  y==2成立  右边为true
		//false & true 结果false
		if(x++==2 & ++y==2){
			x =7;
		}
		System.out.println("x="+x+",y="+y);//x=2,y=2
	}
}
public class LogicExer2{
	public static void main(String[] args){
		int x = 1,y = 1;

		//x==2,x++  左边条件为false,x=2
		//因为短路与,右边不算
		//false && ? 结果是false
		if(x++==2 && ++y==2){
			x =7;
		}
		System.out.println("x="+x+",y="+y);//x=2,y=1
	}
}
public class LogicExer3{
	public static void main(String[] args){
		int x = 1,y = 1;

		//x==1,x++  左边为true,x=2
		//因为是逻辑与,右边继续  
		//++y, y==1  y=2 右边为false
		//条件true | false,最终为true
		if(x++==1 | ++y==1){
			x =7;
		}
		System.out.println("x="+x+",y="+y);//x=7,y=2
	}
}	
public class LogicExer4{
	public static void main(String[] args){
		int x = 1,y = 1;

		//x==1,x++  左边为true,x=2
		//因为是短路或,左边为true,右边就不看了
		//整个条件为true
		if(x++==1 || ++y==1){
			x =7;
		}
		System.out.println("x="+x+",y="+y);//x=7,y=1

	}
}

面试题2

1561431208735

public class LogicExer5{
	public static void main (String []  args)  {
		boolean x = true;
		boolean y = false;
		short z = 42;
		
		//如果if((z++==42)&&(y==true))条件成立,执行z++,不成立,就不执行z++
		//左边的条件:z==42,z++  z==42成立,z++变成43
		//中间虽然是短路与,因为左边现在是true,右边还要看
		//右边 y==true   不成立
		//true && false 结果为false
		if((z++==42)&&(y==true))	z++;
	
		//左边为x=false,赋值  结果就为false
		//中间虽然为短路或,因为左边是false,右边继续看
		//++z,z==45  ++z变成44,z==45是否成立,不成立
		//false || false  结果为false
		if((x=false) || (++z==45))  z++;

		System. out.println("z="+z);//44
	}
}
class  Test4_2  {
	public static void main (String []  args)  {
		boolean x = true;
		boolean y = false;
		short z = 42;
		
		//如果if(y=true)条件成立,接着判断if((z++==42)&&(y==true))	z++;	
		//如果不成立,if((z++==42)&&(y==true))	z++;	不看的
		/*
		if(y = true)
				
		if((z++==42)&&(y==true))	z++;	
		
		if((x=false) || (++z==45))  z++;
		*/
		
		//标准
		//y=true赋值,y就被修改为true,if(true)成立
		if(y=true){
			//左边:z==42,z++  成立,z变成43
			//&&短路与,不满足短路的情况,右边继续
			//y==true 成立
			//true && true,结果为true
			if((z++==42)&&(y==true)){
				//z++变成44
				z++;
			}
		}
		//左边:x=false不成立
		//中间虽然是短路或,但是没满足短路的情况,右边继续
		//++z,z==45  ++z变成45,z==45成立
		if((x=false) || (++z==45)){
			//z++,变成46
			z++;
		}
		System. out.println("z="+z);//46
	}
}

条件运算符

  • 条件运算符格式:
条件表达式?结果1:结果2
  • 条件运算符计算方式:
    • 条件判断的结果是true,条件运算符整体结果为结果1,赋值给变量。
    • 判断条件的结果是false,条件运算符整体结果为结果2,赋值给变量。
public static void main(String[] args) {
    int i = (1==2 ? 100 : 200);
    System.out.println(i);//200
    int j = (3<=4 ? 500 : 600);
    System.out.println(j);//500
}

练习

1、声明三个整型的变量,a,b,c,要求找出最大值
2、声明一个整型的变量,判断它是正数还是负数,还是0

运算符优先级

1553858424335

提示说明:

(1)表达式不要太复杂

(2)先算的使用()

大体的排序:算术->位—>比较—>逻辑—>三元—>赋值

流程控制

不论哪一种编程语言,都会提供两种基本的流程控制结构:分支结构和循环结构。其中分支结构用于实现根据条件来选择性地执行某段代码,循环结构则用于实现根据循环条件重复执行某段代码。

顺序结构

任何编程语言中最常见的程序结构就是顺序结构。顺序结构就是程序从上到下逐行地执行,中间没有任何判断和跳转。如果main方法的多行代码之间没有任何流程控制,则程序总是从上向下依次执行,排在前面的代码先执行,排在后面的代码后执行。

public static void main(String[] args){
    //顺序执行,根据编写的顺序,从上到下运行
    System.out.println(1);
    System.out.println(2);
    System.out.println(3);
}

输入语句

键盘输入代码的三个步骤:

1、准备Scanner类型的变量

2、提示输入xx

3、接收输入内容

示例代码:

//1、准备Scanner类型的变量
//Scanner是一个引用数据类型,它的全名称是java.util.Scanner
//input就是一个引用数据类型的变量了,赋给它的值是一个对象
java.util.Scanner input = new java.util.Scanner(System.in);//System.in默认代表键盘输入

//2、提示输入xx
System.out.print("请输入一个整数:");

//3、接收输入内容
int num = input.nextInt();

//列出各种数据类型的输入
int num = input.nextInt();
long bigNum = input.nextLong();
double d = input.nextDouble();
boolean b = input.nextBoolean();
String s = input.next();
char c = input.next().charAt(0);//先按照字符串接收,然后再取字符串的第一个字符(下标为0)

语法案例演示1:

从键盘输入个人信息

class Day03_Test02_Input{
	public static void main(String[] args){
		//这里变量取什么名,下面就用什么.
		//例如:这里取名input,下面就用input.
		java.util.Scanner input = new java.util.Scanner(System.in);
		
		System.out.print("请输入姓名:");
		String name = input.next();
		
		System.out.print("请输入年龄:");
		int age = input.nextInt();
		
		System.out.print("请输入性别:");
		//input.next()得到字符串,不管你输入几个字符,
		//.charAt(0):从字符串中取出一个字符,(0)表示取第一个字符,(1)表示取第二个字符
		//charAt(index):也是一个方法,从第二个单词开始首字母大写,所以A是大写
		char gender = input.next().charAt(0);
		
		System.out.print("请输入体重:");
		double weight = input.nextDouble();
		
		System.out.print("请输入是否已婚(true/false):");
		boolean isMarry = input.nextBoolean();
		
		System.out.println("姓名:" + name);
		System.out.println("年龄:" + age);
		System.out.println("性别:" + gender);
		System.out.println("体重:" + weight);
		System.out.println("婚否:" + (isMarry?"是":"否"));
	}
}

语法案例演示2:next()与nextLine()

/*
next()方法:
	遇到空格等空白符,就认为输入结束
nextLine()方法:
	遇到回车换行,就认为输入结束
	
如果你在键盘输入过程中,遇到java.util.InputMismatchException,
说明你输入的数据类型与接收数据的变量的类型不匹配
*/
class Day03_Test04_Input2{
	public static void main(String[] args){
		java.util.Scanner input = new java.util.Scanner(System.in);
		
		System.out.print("请输入姓名:");
		//String name = input.next();//张 三  只能接收张,后面的空格和三无法接收,被下面的输入接收
		String name = input.nextLine();
		System.out.println("name = " + name);
		
		System.out.print("请输入年龄:");
		int age = input.nextInt();	//23回车换行  这里只接收23,回车换行被下面的输入接收	
		input.nextLine();//读取23后面的回车换行,但是这个不需要接收,只有下面一个输入是nextLine()情况下才需要这样,如果下面的输入是next()或者是nextInt(),nextDouble()等就不需要这么干
		System.out.println("age = " + age);
		
		System.out.print("请输入电话号码:");
		String tel = input.nextLine();
		System.out.println("tel = " + tel);//此部分输出不出来,只有注释掉才能正确输出
	}
}

分支结构:if语句第一种格式

  • if语句第一种格式: if
if(条件表达式){
  	语句体;
  • 执行流程

    • 首先判断条件表达式看其结果是true还是false

    • 如果是true就执行语句体

    • 如果是false就不执行语句体

语法案例演示1:

public static void main(String[] args){
    System.out.println("开始");
    // 定义两个变量
    int a = 10;
    int b = 20;
    //变量使用if判断
    if (a == b){
      	System.out.println("a等于b");
    }
    int c = 10;
    if(a == c){
      	System.out.println("a等于c");
    }
    System.out.println("结束");

语法案例演示2

案例:从键盘输入年份,请输出该年的2月份的总天数。闰年2月份29天,平年28天。

闰年:
(1)能被4整除,不能被100整除
(2)能被400整除

public class Test {
	public static void main(String[] args) {
		java.util.Scanner input = new java.util.Scanner(System.in);
		System.out.print("请输入年份:");
		int year = input.nextInt();
		int days = 28;
		
		if(year%4==0 && year%100!=0 || year%400==0){
			days++;
		}
		System.out.println(year + "年的2月份共" + days + "天");
		input.close();
	}
}
public class Test {
	public static void main(String[] args) {
		java.util.Scanner input = new java.util.Scanner(System.in);
		System.out.print("请输入年份:");
		int year = input.nextInt();
		int days = 28;
		
		if(year%4==0 && year%100!=0 || year%400==0)
			days++;//当语句块只有一句时,可以省略{},但是建议还是保留比较靠谱
		
		System.out.println(year + "年的2月份共" + days + "天");
		input.close();
	}
}

分支结构:if语句第二种格式

  • if语句第二种格式: if…else
if(关系表达式) { 
  	语句体1;
}else {
  	语句体2;
}
  • 执行流程

    • 首先判断关系表达式看其结果是true还是false

    • 如果是true就执行语句体1

    • 如果是false就执行语句体2

语法案例演示1:

public static void main(String[] args){
    // 判断给定的数据是奇数还是偶数
    // 定义变量
    int a = 1;
    if(a % 2 == 0) {
      	System.out.println("a是偶数");
    } else{
      	System.out.println("a是奇数");
    }
    System.out.println("结束");
}

语法案例演示2:if语句和三元运算符的互换

在某些简单的应用中,if语句是可以和三元运算符互换使用的。

public static void main(String[] args) {
    int a = 10;
    int b = 20;
    //定义变量,保存a和b的较大值
    int max;
    if(a > b) {
      	max = a;
    } else {
      	max = b;
    }
    //可以上述功能改写为三元运算符形式
    max = a > b ? a : b;
}
public static void main(String[] args) {
    int a = 10;
    int b = 20;
    //定义变量,保存a和b的较大值
    int max;
    if(a > b) 
      	max = a;//当语句块只有一个语句时,可以省略{},但是不建议省略{}
     else 
      	max = b;
}

练习:求出最大值

从键盘输入三个数,求出最大值,用单分支if和双分支if..else来计算

class Day03_Test08_MaxValueExer{
	public static void main(String[] args){
		java.util.Scanner input = new java.util.Scanner(System.in);
		
		System.out.print("请输入第1个整数:");
		int a = input.nextInt();
		
		System.out.print("请输入第2个整数:");
		int b = input.nextInt();
		
		System.out.print("请输入第3个整数:");
		int c = input.nextInt();
		
		/*
		int max;//存储三个数中的最大值
		if(a > b){
			max = a;
		}else{
			max = b;
		}
		if(c > max){
			max = c;
		}
		*/
		int max = a>b ? a : b;
		max = max>c ? max : c;
		System.out.println(a+","+b+","+c+"中最大的是:"+ max);
	}
}

分支结构:if语句第三种格式

  • if语句第三种格式: if…else if …else
if (判断条件1) {
  	执行语句1;
} else if (判断条件2) {
  	执行语句2;
}
...
}else if (判断条件n) {
 	执行语句n;
} else {
  	执行语句n+1;
}
  • 执行流程

    • 首先判断关系表达式1看其结果是true还是false

    • 如果是true就执行语句体1,然后结束当前多分支

    • 如果是false就继续判断关系表达式2看其结果是true还是false

    • 如果是true就执行语句体2,然后结束当前多分支

    • 如果是false就继续判断关系表达式…看其结果是true还是false

    • 如果没有任何关系表达式为true,就执行语句体n+1,然后结束当前多分支。

语法案例演示1:

计算如下函数:x和y的关系满足如下:
(1)x>=3; y = 2x + 1;
(2)-1<=x<3; y = 2x;
(3)x<-1; y = 2x – 1;
从键盘输入x的值,计算出y的值并输出。

public static void main(String[] args) {
    java.util.Scanner input = new java.util.Scanner(System.in);
    System.out.print("请输入x的值:");
    int x = input.nextInt();
    int y;
    if (x>= 3) {
      	y = 2 * x + 1;
    } else if (x >= -1 && x < 3) {
      	y = 2 * x;
    } else  {
      	y = 2 * x - 1;
    }
    System.out.println("y的值是:"+y);
}
public static void main(String[] args) {
    java.util.Scanner input = new java.util.Scanner(System.in);
    System.out.print("请输入x的值:");
    int x = input.nextInt();
    int y;
    if (x>= 3) {
      	y = 2 * x + 1;
    } else if (x >= -1) {
      	y = 2 * x;
    } else  {
      	y = 2 * x - 1;
    }
    System.out.println("y的值是:"+y);
}

1561700798198

1561700825016

语法案例演示2:

  • 通过指定考试成绩,判断学生等级
    • 90-100 优秀
    • 80-89 好
    • 70-79 良
    • 60-69 及格
    • 60以下 不及格
public static void main(String[] args) {	
    int score = 89if(score<0 || score>100){
      	System.out.println("你的成绩是错误的");
    }else if(score>=90 && score<=100){
      	System.out.println("你的成绩属于优秀");
    }else if(score>=80 && score<90){
      	System.out.println("你的成绩属于好");
    }else if(score>=70 && score<80){
      	System.out.println("你的成绩属于良");
    }else if(score>=60 && score<70){
      	System.out.println("你的成绩属于及格");
    }else {
      	System.out.println("你的成绩属于不及格");
    }	
}

1561436569004

public static void main(String[] args) {	
    int score = 89;
    if(score<0 || score>100){
      	System.out.println("你的成绩是错误的");
    }else if(score>=90){
      	System.out.println("你的成绩属于优秀");
    }else if(score>=80){
      	System.out.println("你的成绩属于好");
    }else if(score>=70){
      	System.out.println("你的成绩属于良");
    }else if(score>=60){
      	System.out.println("你的成绩属于及格");
    }else {
      	System.out.println("你的成绩属于不及格");
    }	
}

-

分支结构:if..else嵌套

在if的语句块中,或者是在else语句块中,
又包含了另外一个条件判断(可以是单分支、双分支、多分支)

执行的特点:
(1)如果是嵌套在if语句块中的
只有当外部的if条件满足,才会去判断内部的条件
(2)如果是嵌套在else语句块中的
只有当外部的if条件不满足,进入else后,才会去判断内部的条件

语法案例演示1:

public static void main(String[] args) {	
    int score = 89;
    if(score<0 || score>100){
      	System.out.println("你的成绩是错误的");
    }else{
    	if(score>=90){
	      	System.out.println("你的成绩属于优秀");
	    }else if(score>=80){
	      	System.out.println("你的成绩属于好");
	    }else if(score>=70){
	      	System.out.println("你的成绩属于良");
	    }else if(score>=60){
	      	System.out.println("你的成绩属于及格");
	    }else {
	      	System.out.println("你的成绩属于不及格");
	    }	
    }
}
//省略{}的情况,else中嵌套了一个完整的多分支结构,也算是一个语句,称为复合语句,所以也可以省略{}
public static void main(String[] args) {	
    int score = 89;
    if(score<0 || score>100)
      	System.out.println("你的成绩是错误的");
    else
    	if(score>=90){
	      	System.out.println("你的成绩属于优秀");
	    }else if(score>=80){
	      	System.out.println("你的成绩属于好");
	    }else if(score>=70){
	      	System.out.println("你的成绩属于良");
	    }else if(score>=60){
	      	System.out.println("你的成绩属于及格");
	    }else {
	      	System.out.println("你的成绩属于不及格");
	    }
}

语法案例演示2:

从键盘输入一个年份,和月份,输出该年份该月的总天数

要求:年份为正数,月份1-12

public static void main(String[] args){
	//从键盘输入一个年份,和月份
	java.util.Scanner input = new java.util.Scanner(System.in);
	
	System.out.print("年份:");
	int year = input.nextInt();
	
	System.out.print("月份:");
	int month = input.nextInt();
	
	if(year>0){
		if(month>=1 && month<=12){
			//合法的情况
			int days;
			if(month==2){
				if(year%4==0 && year%100!=0 || year%400==0){
					days = 29;
				}else{
					days = 28;
				}
			}else if(month==4 || month==6  || month==9 || month==11){
				days = 30;
			}else{
				days = 31;
			}
			System.out.println(year+"年" + month + "月有" + days +"天");
		}else{
			System.out.println("月份输入不合法");
		}
	}else{
		System.out.println("年份输入不合法");
	}
}

分支结构:switch选择结构

语法格式:

switch(表达式){
    case 常量值1:
        语句块1;break;case 常量值2:
        语句块2;break;】   
    。。。
   【default:
        语句块n+1;break;】
     】
}

执行过程:

(1)入口

①当switch(表达式)的值与case后面的某个常量值匹配,就从这个case进入;

②当switch(表达式)的值与case后面的所有常量值都不匹配,寻找default分支进入;不管default在哪里

(2)一旦从“入口”进入switch,就会顺序往下执行,直到遇到“出口”,即可能发生贯穿

(3)出口

①自然出口:遇到了switch的结束

②中断出口:遇到了break等

注意:

(1)switch(表达式)的值的类型,只能是:4种基本数据类型(byte,short,int,char),两种引用数据类型(JDK1.5之后枚举、JDK1.7之后String)

(2)case后面必须是常量值,而且不能重复

语法案例演示1:

public class SwitchDemo01 {
	public static void main(String[] args) {
		//定义指定的星期
		int weekday = 5;
		
		//switch语句实现选择
		switch(weekday) {
            case 1:
                System.out.println("星期一");
                break;
            case 2:
                System.out.println("星期二");
                break;
            case 3:
                System.out.println("星期三");
                break;
            case 4:
                System.out.println("星期四");
                break;
            case 5:
                System.out.println("星期五");
                break;
            case 6:
                System.out.println("星期六");
                break;
            case 7:
                System.out.println("星期日");
                break;
            default:
                System.out.println("你的数字有误");
                break;
		}
	}
}

语法案例演示2:case的穿透性

在switch语句中,如果case的后面不写break,将出现穿透现象,也就是一旦匹配成功,不会在判断下一个case的值,直接向后运行,直到遇到break或者整个switch语句结束,switch语句执行终止。

练习:根据指定的月份输出对应季节(if语句)

/*
 * 需求:定义一个月份,输出该月份对应的季节。
 * 		一年有四季
 * 		3,4,5	春季
 * 		6,7,8	夏季
 * 		9,10,11	秋季
 * 		12,1,2	冬季
 * 
 * 分析:
 * 		A:指定一个月份
 * 		B:判断该月份是几月,根据月份输出对应的季节
 * 			if
 * 			switch
 */
public class SwitchTest01 {
	public static void main(String[] args) {
		//指定一个月份
		int month = 5;
		
		/*
		if (month == 1) {
			System.out.println("冬季");
		} else if (month == 2) {
			System.out.println("冬季");
		} else if (month == 3) {
			System.out.println("春季");
		} else if (month == 4) {
			System.out.println("春季");
		} else if (month == 5) {
			System.out.println("春季");
		} else if (month == 6) {
			System.out.println("夏季");
		} else if (month == 7) {
			System.out.println("夏季");
		} else if (month == 8) {
			System.out.println("夏季");
		} else if (month == 9) {
			System.out.println("秋季");
		} else if (month == 10) {
			System.out.println("秋季");
		} else if (month == 11) {
			System.out.println("秋季");
		} else if (mouth == 12) {
			System.out.println("冬季");
        } else {
            System.out.println("你输入的月份有误");
        }
		*/
		
		// 改进版
		if ((month == 1) || (month == 2) || (month == 12)) {
			System.out.println("冬季");
		} else if ((month == 3) || (month == 4) || (month == 5)) {
			System.out.println("春季");
		} else if ((month == 6) || (month == 7) || (month == 8)) {
			System.out.println("夏季");
		} else if ((month == 9) || (month == 10) || (month == 11)) {
			System.out.println("秋季");
		} else {
			System.out.println("你输入的月份有误");
		}
	}
}

练习:根据指定的月份输出对应季节(switch语句)

/*
 * 需求:指定一个月份,输出该月份对应的季节。
 * 		一年有四季
 * 		3,4,5	春季
 * 		6,7,8	夏季
 * 		9,10,11	秋季
 * 		12,1,2	冬季
 * 
 * 分析:
 * 		A:指定一个月份
 * 		B:判断该月份是几月,根据月份输出对应的季节
 * 			if
 * 			switch
 */
public class SwitchTest02 {
	public static void main(String[] args) {
		//指定一个月份
		int month = 5;
		
		/*
		switch(month) {
            case 1:
                System.out.println("冬季");
                break;
            case 2:
                System.out.println("冬季");
                break;
            case 3:
                System.out.println("春季");
                break;
            case 4:
                System.out.println("春季");
                break;
            case 5:
                System.out.println("春季");
                break;
            case 6:
                System.out.println("夏季");
                break;
            case 7:
                System.out.println("夏季");
                break;
            case 8:
                System.out.println("夏季");
                break;
            case 9:
                System.out.println("秋季");
                break;
            case 10:
                System.out.println("秋季");
                break;
            case 11:
                System.out.println("秋季");
                break;
            case 12:
                System.out.println("冬季");
                break;
            default:
                System.out.println("你输入的月份有误");
                break;
		}
		*/
	
        // 改进版 
		switch(month) {
            case 1:
            case 2:
            case 12:
                System.out.println("冬季");
                break;
            case 3:
            case 4:
            case 5:
                System.out.println("春季");
                break;
            case 6:
            case 7:
            case 8:
                System.out.println("夏季");
                break;
            case 9:
            case 10:
            case 11:
                System.out.println("秋季");
                break;
            default:
                System.out.println("你输入的月份有误");
                break;
		}
	}
}

常见错误实现1:

switch(month){
	case 3|4|5://3|4|5 用了位运算符,11 | 100 | 101结果是 1117
		System.out.println("春季");
		break;
	case 6|7|8://6|7|8用了位运算符,110 | 111 | 1000结果是111115
		System.out.println("夏季");
		break;
	case 9|10|11://9|10|11用了位运算符,1001 | 1010 | 1011结果是101111
		System.out.println("秋季");
		break;
	case 12|1|2://12|1|2 用了位运算符,1100 | 1 | 10 结果是1111,是15
		System.out.println("冬季");
		break;
	default:
		System.out.println("输入有误");
}

常见错误实现2:

//编译不通过
switch(month){
	case 3,4,5:
		System.out.println("春季");
		break;
	case 6,7,8:
		System.out.println("夏季");
		break;
	case 9,10,11:
		System.out.println("秋季");
		break;
	case 12,1,2:
		System.out.println("冬季");
		break;
	default:
		System.out.println("输入有误");
}

Switch 表达式也是作为预览语言功能的第一个语言改动被引入Java12 中,开始支持如下写法:

switch(month) {
	case 3,4,5 -> System.out.println("春季");
	case 6,7,8 -> System.out.println("夏季");
	case 9,10,11 -> System.out.println("秋季");
	case 12,1,2 -> System.out.println("冬季");
	default->System.out.println("月份输入有误!");
};

★★★★

循环结构:while循环

需求:打印10次的HelloWorld

public class ForDemo01 {
	public static void main(String[] args) {
    	//控制台输出10次HelloWorld,不使用循环
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("HelloWorld");
		System.out.println("-------------------------");

		//用循环改进,循环10次
		//定义变量从10开始,循环条件为<=10
		int i = 1;
        while(i <= 10){
            System.out.println("HelloWorld");
            i++;
        }
	}
}

1、while循环语句标准格式:

while (循环条件语句①) {
    循环体语句②;
}
while(true){
     循环体语句;//如果此时循环体中没有跳出循环的语句,也是死循环
}

注意:

while(循环条件)中循环条件必须是boolean类型

执行流程:

  • 第一步:执行循环条件语句①,看循环条件语句的值是true,还是false;
    • 如果是true,执行第二步;
    • 如果是false,循环语句中止,循环不再执行。
  • 第二步:执行循环体语句②;
  • 第三步:循环体语句执行完后,重新从第一步开始再执行一遍

2、while循环语句扩展格式:

初始化语句①;
while (循环条件语句②) {
    循环体语句③;
    迭代语句④;
}

执行流程:

  • 第一步:执行初始化语句①,完成循环变量的初始化;
  • 第二步:执行循环条件语句②,看循环条件语句的值是true,还是false;
    • 如果是true,执行第三步;
    • 如果是false,循环语句中止,循环不再执行。
  • 第三步:执行循环体语句③
  • 第四步:执行迭代语句④,针对循环变量重新赋值
  • 第五步:根据循环变量的新值,重新从第二步开始再执行一遍

语法演示案例1:求1加到100的和

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

循环结构:do…while循环

1、do…while循环语句标准格式:

do {
    循环体语句①;
} while (循环条件语句②)

注意:

(1)while(循环条件)中循环条件必须是boolean类型

(2)do{}while();最后有一个分号

(3)do…while结构的循环体语句是至少会执行一次,这个和for和while是不一样的

执行流程:

  • 第一步:执行循环体语句①;
  • 第二步:执行循环条件语句②,看循环条件语句的值是true,还是false;
    • 如果是true,执行第三步;
    • 如果是false,循环语句终止,循环不再执行。
  • 第三步:循环条件语句执行完后,重新从第一步开始再执行一遍

2、do…while循环语句扩展格式:

初始化语句①
do {
    循环体语句②;
    迭代语句③;
} while (循环条件语句④)

执行流程:

  • 第一步:执行初始化语句①,完成循环变量的初始化;
  • 第二步:执行循环体语句②;
  • 第三步:执行迭代语句③,针对循环变量重新赋值;
  • 第四步:执行循环条件语句④,看循环条件语句的值是true,还是false;
    • 如果是true,根据循环变量的新值,重新从第二步开始再执行一遍;
    • 如果是false,循环语句中止,循环不再执行。

语法演示案例2:输入密码和确认密码,比较是否一致

/*	输入密码和确认密码,如果两次密码一致则显示注册成功,两次密码不一致则提示重新输入	*/	Scanner input = new Scanner(System.in);    String pwdOne = "";	String pwdTwo = "";	do{        System.out.println("请输入密码");        pwdOne = input.next();        System.out.println("请输入确认密码");        pwdTwo = input.next();        if(!pwdOne.equals(pwdTwo)){            System.out.println("两次密码不一致,请重新输入");        }    }while(!pwdOne.equals(pwdTwo));	System.out.println("注册成功");

练习:猜数

随机生成一个100以内的数,猜数字游戏

从键盘输入数,如果大了提示,大了,如果小了,提示小了,如果对了,就不再猜了,并统计一共猜了多少次

提示:随机数 Math.random()

double num = Math.random();// [0,1)的小数

public static void main(String[] args){
	//随机生成一个100以内的整数
	/*
	Math.random() ==> [0,1)的小数
	Math.random()* 100 ==> [0,100)的小数
	(int)(Math.random()* 100) ==> [0,100)的整数
	*/
	int num = (int)(Math.random()* 100);
	//System.out.println(num);
	
	//声明一个变量,用来存储猜的次数
	int count = 0;
	
	java.util.Scanner input = new java.util.Scanner(System.in);
	int guess;//提升作用域
	do{
		System.out.print("请输入100以内的整数:");
		guess = input.nextInt();
		
		//输入一次,就表示猜了一次
		count++;
		
		if(guess > num){
			System.out.println("大了");
		}else if(guess < num){
			System.out.println("小了");
		}
	}while(num != guess);
	
	System.out.println("一共猜了:" + count+"次");
	
}

循环语句:for循环

循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要通过修改循环变量使得循环判断条件为false,从而结束循环,否则循环将一直执行下去,形成死循环。

1、for循环语句格式:

for(初始化语句①; 循环条件语句②; 迭代语句④){
	循环体语句③
}
for(;;){
    循环体语句块;//如果循环体中没有跳出循环体的语句,那么就是死循环
}

注意:

(1)for(;;)中的两个;是不能多也不能少

(2)循环条件必须是boolean类型

(3)如果循环条件语句②省略的话,就默认为循环条件成立

执行流程:

  • 第一步:执行初始化语句①,完成循环变量的初始化;
  • 第二步:执行循环条件语句②,看循环条件语句的值是true,还是false;
    • 如果是true,执行第三步;
    • 如果是false,循环语句中止,循环不再执行。
  • 第三步:执行循环体语句③
  • 第四步:执行迭代语句④,针对循环变量重新赋值
  • 第五步:根据循环变量的新值,重新从第二步开始再执行一遍

语法演示案例3:求出1-100之间偶数和

/*
 * 练习:求出1-100之间偶数和
 * 
 * 分析:
 * 		1.定义求和变量,初始化值是0
 * 		2.获取1-100之间的数据,用for循环实现
 * 		3.把获取到的数据进行判断,看是否是偶数
 * 			如果是,就累加
 * 		4.输出求和结果
 */
public class ForTest03 {
	public static void main(String[] args) {
		//定义求和变量,初始化值是0
		int sum = 0;
		
		//获取1-100之间的数据,用for循环实现
		for(int x=1; x<=100; x++) {
			//把获取到的数据进行判断,看是否是偶数
			if(x % 2 == 0) {
				sum += x;
			}
		}
		
		//输出求和结果
		System.out.println("sum:"+sum);
	}
}

循环语句的区别

  • 从循环次数角度分析
    • do…while循环至少执行一次循环体语句
    • for和while循环先循环条件语句是否成立,然后决定是否执行循环体,至少执行零次循环体语句
  • 从循环变量的生命周期角度分析

    • for循环的循环变量在for()中声明的,在循环语句结束后,不可以被访问;
    • while和do…while循环的循环变量因为在外面声明的,所以while和do…while结束后可以被继续使用的;
  • 如何选择

    • 遍历有明显的循环次数(范围)的需求,选择for循环
    • 遍历没有明显的循环次数(范围)的需求,循环while循环
    • 如果循环体语句块至少执行一次,可以考虑使用do…while循环
    • 本质上:三种循环之间是可以互相转换的,都能实现循环的功能
  • 三种循环结构都具有四要素:

    • (1)循环变量的初始化表达式
    • (2)循环条件
    • (3)循环变量的修改的迭代表达式
    • (4)循环体语句块

控制语句

break

  • 使用场景:终止switch或者当前循环

    • 在选择结构switch语句中

    • 在循环语句中

    • 离开使用场景的存在是没有意义的

语法案例演示1:判断某个数是否是素数

案例:从键盘输入一个大于1的自然数,判断它是否是素数
提示:素数是指大于1的自然数中,除了1和它本身以外不能再有其他因数的自然数,即某个素数n,在[2,n-1]范围内没有其他自然数可以把n整除

class Test07BreakExer1{
	public static void main(String[] args){
		java.util.Scanner input = new java.util.Scanner(System.in);

		int num;
		while(true){
			//true是常量,常量是编译期间就可以确定的值
			System.out.print("请输入一个大于1的自然数:");
			num = input.nextInt();
			
			if(num>1){
				break;
			}
		}
		System.out.println("num = " + num);
		
		boolean flag = true;//假设num是素数
		
		//判断它是否是素数
		for(int i=2; i<num; i++){
			if(num % i ==0){//num被某个i整除了,num就不是素数
				System.out.println(num + "不是素数");
				flag = false;
				break;//找到其中一个可以把num整除的数,就可以结束了,因为num已经可以判定不是素数了
			}
		}
		
		//只有把[2,num-1]之间的所有数都检查过了,才能下定结论,num是素数
		if(flag){
			System.out.println(num + "是素数");
		}
		
	}
}
class Test07BreakExer1_2{
	public static void main(String[] args){
		java.util.Scanner input = new java.util.Scanner(System.in);
		
		int num;
		while(true){
			//true是常量,常量是编译期间就可以确定的值
			System.out.print("请输入一个大于1的自然数:");
			num = input.nextInt();
			
			if(num>1){
				break;
			}
		}
		System.out.println("num = " + num);
		
		boolean flag = true;//假设num是素数
		
		//判断它是否是素数
		//在[2, num的平方根]之间如果都没有一个自然数可以把num整除,那么num就是素数
		/*
		不是素数
		9的平方根是3,除1和它本身外的因数:3
		16的平方根是4,除1和它本身外的因数:2,4,8
		25的平方根是5,除1和它本身外的因数:5
		是素数
		7的平方根是2.64,除1和它本身外的只需要判断2,如果2不是,那么就不是
				无须判断,3,4,5,6
		*/
		for(int i=2; i<=Math.sqrt(num); i++){
			if(num % i ==0){
				System.out.println(num + "不是素数");
				flag = false;
				break;
			}
		}
		
		System.out.println(num + (flag?"是":"不是") + "素数");
	}
}

语法案例演示2:统计正数、负数个数

案例:从键盘输入不断输入整数,输入0表示结束,统计一共有几个正数、负数。

public static void main(String[] args) {
	java.util.Scanner input = new java.util.Scanner(System.in);
	
	int positive = 0;
	int negative = 0;
	while(true){
		System.out.print("请输入整数(0)结束:");
		int num = input.nextInt();
		if(num==0){
			break;
		}else if(num>0){
			positive++;
		}else{
			negative++;
		}
	}
	System.out.println("正数:" + positive + ",负数:" + negative);
}

语法案例演示3:break同时存在switch和循环中

=========ATM=======
1、存款
2、取款
3、显示余额
4、退出
请选择:

public static void main(String[] args){
	java.util.Scanner input = new java.util.Scanner(System.in);
	
	//声明一个变量表示余额
	double balance = 0.0;
	boolean flag = true;
	while(flag){
		System.out.println("=========ATM=======");
		System.out.println("\t1、存款");
		System.out.println("\t2、取款");
		System.out.println("\t3、显示余额");
		System.out.println("\t4、退出");
		System.out.print("请选择:");
		int select = input.nextInt();
		
		switch(select){
			case 1:
				System.out.print("存款的金额:");
				double money = input.nextDouble();
				balance += money;
				break;
			case 2:
				System.out.print("取款的金额:");
				money = input.nextDouble();
				balance -= money;
				break;	
			case 3:
				System.out.println("现在的余额:" + balance);
				break;
			case 4:
				flag = false;
				break;//只能结束switch
		}
	}
}

continue

  • 使用场景:结束本次循环,继续下一次的循环
public static void main(String[] args) {
    for (int i = 1; i <= 10; i++) {
        //需求:不打印3的倍数
        if(i % 3 == 0){
            continue;
        }
        System.out.println(i);
    }
}

练习:打印1-100之间的整数,跳过7的倍数和7结尾的数

public static void main(String[] args){
	//打印1-100之间的整数,跳过7的倍数和7结尾的数
	for(int i=1; i<=100; i++){
		if(i%7==0 || i%10==7){
			continue;
			//break;
		}
		System.out.println(i);
	}
}

嵌套循环

  • 所谓嵌套循环,是指一个循环的循环体是另一个循环。比如for循环里面还有一个for循环,就是嵌套循环。总共的循环次数=外循环次数*内循环次数。当然可以是三种循环任意互相嵌套。
  • 嵌套循环格式:
for(初始化语句①; 循环条件语句②; 迭代语句⑦) {
    for(初始化语句③; 循环条件语句④; 迭代语句⑥) {
      	循环体语句⑤;
    }
}

语法案例演示1:打印5行5列矩形

public static void main(String[] args){
	for (int i = 0; i < 5; i++) {
		for (int j = 0; j < 5; j++) {
			System.out.print("*");
		}
		System.out.println();
	}
}

语法案例演示2:打印5行直角三角形

*
**
***
****
*****
public static void main(String[] args){
	for (int i = 0; i < 5; i++) {
		for (int j = 0; j <= i; j++) {
			System.out.print("*");
		}
		System.out.println();
	}
}

练习1:

/*
1
12
123
1234
12345
*/
	public static void main(String[] args){
		//外循环控制行数
		for(int i=1; i<=5; i++){
			//内循环控制每一行打印的数字
			/*
			当i=1,外循环第1次,第1行,j=1
			当i=2,外循环第2次,第2行,j=1,2
			当i=3,外循环第3次,第3行,j=1,2,3
			当i=4,外循环第4次,第4行,j=1,2,3,4
			当i=5,外循环第5次,第5行,j=1,2,3,4,5
			j=1,j<=i
			*/
			for(int j=1; j<=i; j++){
				System.out.print(j);
			}
			System.out.println();
		}
	}

练习2:

/*
1
22
333
4444
55555
*/
	public static void main(String[] args){
		for(int i=1; i<=5; i++){
			//内循环控制每一行打印的数字
			/*
			当i=1,外循环第1次,第1行,1,    	1个i,j=1
			当i=2,外循环第2次,第2行,22		2个i,j=1,2
			当i=3,外循环第3次,第3行,333		3个i,j=1,2,3
			当i=4,外循环第4次,第4行,4444		4个i,j=1,2,3,4
			当i=5,外循环第5次,第5行,55555	5个i,j=1,2,3,4,5
			说明,打印的是i的值,打印几个i
			j=1,j<=i
			*/
			for(int j=1; j<=i; j++){
				System.out.print(i);
			}
			System.out.println();
		}
	}

数组

容器概述

案例分析

现在需要统计某公司员工的工资情况,例如计算平均工资、找到最高工资等。假设该公司有50名员工,用前面所学的知识,程序首先需要声明50个变量来分别记住每位员工的工资,然后在进行操作,这样做会显得很麻烦,而且错误率也会很高。因此我们可以使用容器进行操作。将所有的数据全部存储到一个容器中,统一操作。

容器概念

  • 容器:是将多个数据存储到一起,每个数据称为该容器的元素。
  • 生活中的容器:水杯,衣柜,教室

数组的概念

  • 数组概念: 数组就是用于存储数据的长度固定的容器,保证多个数据的数据类型要一致。

百度百科中对数组的定义:

所谓数组(array),就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,以便统一管理他们,然后用编号区分他们,这个名字称为数组名,编号称为下标或索引(index)。组成数组的各个变量称为数组的元素(element)。数组中元素的个数称为数组的长度(length)。

1561452334825

数组的特点:

1、数组的长度一旦确定就不能修改

2、创建数组时会在内存中开辟一整块连续的空间。

3、存取元素的速度快,因为可以通过[下标],直接定位到任意一个元素。

数组的声明与初始化

数组的声明:

//推荐
元素的数据类型[] 二维数组的名称;

//不推荐
元素的数据类型  二维数组名[];

静态初始化

  • 格式:
数据类型[] 数组名 = {元素1,元素2,元素3...};//必须在一个语句中完成,不能分开两个语句写
  • 举例:

定义存储1,2,3,4,5整数的数组容器

int[] arr = {1,2,3,4,5};//正确

int[] arr;
arr = {1,2,3,4,5};//错误

静态初始化

  • 格式:
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};
或
数据类型[] 数组名;
数组名 = new 数据类型[]{元素1,元素2,元素3...};
  • 举例:

定义存储1,2,3,4,5整数的数组容器。

int[] arr = new int[]{1,2,3,4,5};//正确

int[] arr;
arr = new int[]{1,2,3,4,5};//正确

int[] arr = new int[5]{1,2,3,4,5};//错误的,后面有{}指定元素列表,就不需要在[长度]指定长度。

动态初始化

  • 格式:
数组存储的元素的数据类型[] 数组名字 = new 数组存储的元素的数据类型[长度];

 或

数组存储的数据类型[] 数组名字;
数组名字 = new 数组存储的数据类型[长度];
  • 数组定义格式详解:
    • 数组存储的元素的数据类型: 创建的数组容器可以存储什么数据类型的数据。
    • 元素的类型可以是任意的Java的数据类型。例如:int, String, Student等
    • [] : 表示数组。
    • 数组名字:为定义的数组起个变量名,满足标识符规范,可以使用名字操作数组。
    • new:关键字,创建数组使用的关键字。因为数组本身是引用数据类型,所以要用new创建数组对象。
    • [长度]:数组的长度,表示数组容器中可以存储多少个元素。
    • 注意:数组有定长特性,长度一旦指定,不可更改。
      • 和水杯道理相同,买了一个2升的水杯,总容量就是2升,不能多也不能少。
  • 举例:

定义可以存储5个整数的数组容器,代码如下:

int[] arr = new int[5];

int[] arr;
arr = new int[5];

思考:用这种方式初始化的数组,元素有值吗?

数组元素的访问

  • 索引: 每一个存储到数组的元素,都会自动的拥有一个编号,从0开始,这个自动编号称为数组索引(index),可以通过数组的索引访问到数组中的元素。
  • 索引范围:[0, 数组的长度-1]
  • 格式:
数组名[索引]
  • 索引访问数组中的元素:
    • 数组名[索引]=数值,为数组中的元素赋值
    • 变量=数组名[索引],获取出数组中的元素
public static void main(String[] args) {
    //定义存储int类型数组,赋值元素1,2,3,4,5
    int[] arr = {1,2,3,4,5};
    //为0索引元素赋值为6
    arr[0] = 6;
    //获取数组0索引上的元素
    int i = arr[0];
    System.out.println(i);
    //直接输出数组0索引元素
    System.out.println(arr[0]);
}

数组的遍历

  • 数组的长度属性: 每个数组都具有长度,而且是固定的,Java中赋予了数组的一个属性,可以获取到数组的长度,语句为:数组名.length ,属性length的执行结果是数组的长度,int类型结果。由次可以推断出,数组的最大索引值为数组名.length-1
  • 数组遍历: 就是将数组中的每个元素分别获取出来,就是遍历。遍历也是数组操作中的基石。
public static void main(String[] args) {
  	int[] arr = new int[]{1,2,3,4,5};
  	//打印数组的属性,输出结果是5
  	System.out.println("数组的长度:" + arr.length);
    
    //遍历输出数组中的元素
    System.out.println("数组的元素有:");
    for(int i=0; i<arr.length; i++){
        System.out.println(arr[i]);
    }
}

数组元素的默认值

当我们使用动态初始化创建数组时:

数组存储的元素的数据类型[] 数组名字 = new 数组存储的元素的数据类型[长度];

此时只确定了数组的长度,那么数组的元素是什么值呢?

数组的元素有默认值:

1561509460135

数组内存图

内存概述

内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。

Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。

Java虚拟机的内存划分

为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

1561465258546

区域名称 作用
程序计数器 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址
本地方法栈 当程序中调用了native的本地方法时,本地方法执行期间的内存区域
方法区 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
堆内存 存储对象(包括数组对象),new来创建的,都存储在堆内存。
虚拟机栈 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。

数组在内存中的存储

一个数组内存图

public static void main(String[] args) {
  	int[] arr = new int[3];
  	System.out.println(arr);//[I@5f150435
}

思考:打印arr为什么是[I@5f150435,它是数组的地址吗?

答:它不是数组的地址。

问?不是说arr中存储的是数组对象的首地址吗?

答:arr中存储的是数组的首地址,但是因为数组是引用数据类型,打印arr时,会自动调用arr数组对象的toString()方法,默认该方法实现的是对象类型名@该对象的hashCode()值的十六进制值。

问?对象的hashCode值是否就是对象内存地址?

答:不一定,因为这个和不同品牌的JVM产品的具体实现有关。例如:Oracle的OpenJDK中给出了5种实现,其中有一种是直接返回对象的内存地址,但是OpenJDK默认没有选择这种方式。

数组下标为什么是0开始

因为第一个元素距离数组首地址间隔0个单元。

两个数组内存图

public static void main(String[] args) {
    int[] arr = new int[3];
    int[] arr2 = new int[2];
    System.out.println(arr);
    System.out.println(arr2);
}

两个变量指向一个数组

public static void main(String[] args) {
    // 定义数组,存储3个元素
    int[] arr = new int[3];
    //数组索引进行赋值
    arr[0] = 5;
    arr[1] = 6;
    arr[2] = 7;
    //输出3个索引上的元素值
    System.out.println(arr[0]);
    System.out.println(arr[1]);
    System.out.println(arr[2]);
    //定义数组变量arr2,将arr的地址赋值给arr2
    int[] arr2 = arr;
    arr2[1] = 9;
    System.out.println(arr[1]);
}

数组的练习题

1、练习1:用一个数组存储26个小写英文字母,并遍历显示,显示要求如:a->A

2、练习2:用一个数组存储本组学员的年龄,从键盘输入,并遍历显示

3、练习3:用一个数组存储本组学员的姓名,从键盘输入,并遍历显示

4、练习4:用数组存储一个星期的7个英文单词,然后从键盘输入星期的值[1-7],输出对应的英文单词

数组的常见算法

数组统计:求总和、均值、统计偶数个数等

思路:遍历数组,挨个的累加,判断每一个元素

示例代码:

int[] arr = {4,5,6,1,9};
//求总和、均值
int sum = 0;//因为0加上任何数都不影响结果
for(int i=0; i<arr.length; i++){
    sum += arr[i];
}
double avg = (double)sum/arr.length;

示例代码2:

int[] arr = {4,5,6,1,9};

//求总乘积
long result = 1;//因为1乘以任何数都不影响结果
for(int i=0; i<arr.length; i++){
    result *= arr[i];
}

示例代码3:

int[] arr = {4,5,6,1,9};
//统计偶数个数
int even = 0;
for(int i=0; i<arr.length; i++){
    if(arr[i]%2==0){
        even++;
    }
}

数组的顺序查找

顺序查找:挨个查看

要求:对数组元素的顺序没要求

顺序查找示例代码:

//查找value第一次在数组中出现的index
public static void main(String[] args){
    int[] arr = {4,5,6,1,9};
    int value = 1;
    int index = -1;

    for(int i=0; i<arr.length; i++){
        if(arr[i] == value){
            index = i;
            break;
        }
    }

    if(index==-1){
        System.out.println(value + "不存在");
    }else{
        System.out.println(value + "的下标是" + index);
    }
}

数组找最值

1574577970893

思路:

(1)先假设第一个元素最大/最小

(2)然后用max/min与后面的元素一一比较

示例代码:

int[] arr = {4,5,6,1,9};
//找最大值
int max = arr[0];
for(int i=1; i<arr.length; i++){
    if(arr[i] > max){
        max = arr[i];
    }
}

数组中找最值及其下标

情况一:找最值及其第一次出现的下标

思路:

(1)先假设第一个元素最大/最小

(2)用max/min变量表示最大/小值,用max/min与后面的元素一一比较

(3)用index时刻记录目前比对的最大/小的下标

示例代码:

int[] arr = {4,5,6,1,9};
//找最大值
int max = arr[0];
int index = 0;
for(int i=1; i<arr.length; i++){
    if(arr[i] > max){
        max = arr[i];
        index = i;
    }
}

思路:

(1)先假设第一个元素最大/最小

(2)用maxIndex时刻记录目前比对的最大/小的下标,那么arr[maxIndex]就是目前的最大值

int[] arr = {4,5,6,1,9};
//找最大值
int maxIndex = 0;
for(int i=1; i<arr.length; i++){
    if(arr[i] > arr[maxIndex]){
        maxIndex = i;
    }
}
System.out.println("最大值:" + arr[maxIndex]);

情况二:找最值及其所有最值的下标(即可能最大值重复)

思路:

(1)先找最大值

①假设第一个元素最大

②用max与后面的元素一一比较

(2)遍历数组,看哪些元素和最大值是一样的

示例代码:

int[] arr = {4,5,6,1,9};
//找最大值
int max = arr[0];
for(int i=1; i<arr.length; i++){
    if(arr[i] > max){
        max = arr[i];
    }
}

//遍历数组,看哪些元素和最大值是一样的
for(int i=0; i<arr.length; i++){
    if(max == arr[i]){
        System.out.print(i+"\t");
    }
}

冒泡排序

Java中的经典算法之冒泡排序(Bubble Sort)

原理:比较两个相邻的元素,将值大的元素交换至右端。

思路:依次比较相邻的两个数,将小数放到前面,大数放到后面。

​ 即第一趟,首先比较第1个和第2个元素,将小数放到前面,大数放到后面。

​ 然后比较第2个和第3个元素,将小数放到前面,大数放到后面。

​ 如此继续,直到比较最后两个数,将小数放到前面,大数放到后面。

​ 重复第一趟步骤,直至全部排序完成。

例如:冒泡:从小到大,从左到右两两比较
/*
{6,3,8,2,9,1}
	第一轮:
	第1次:arr[0]与arr[1]比较,6>3成立,就交换,{3,6,8,2,9,1}
	第2次:arr[1]与arr[2]比较,6>8不成立,不交换{3,6,8,2,9,1}
	第3次:arr[2]与arr[3]比较,8>2成立,就交换,{3,6,2,8,9,1}
	第4次:arr[3]与arr[4]比较,8>9不成立,不交换{3,6,2,8,9,1}
	第5次:arr[4]与arr[5]比较,9>1成立,就交换,{3,6,2,8,1,9}
	
	第一轮结果:{3,6,2,8,1,9}   9已经到达正确位置,下一轮不用在参与

	第二轮:
	第1次:arr[0]与arr[1]比较,3>6不成立,不交换{3,6,2,8,1,9}
	第2次:arr[1]与arr[2]比较,6>2成立,就交换,{3,2,6,8,1,9}
	第3次:arr[2]与arr[3]比较,6>8不成立,不交换{3,2,6,8,1,9}
	第4次:arr[3]与arr[4]比较,8>1成立,就交换,{3,2,6,1,8,9}
	
	第二轮结果:{3,2,6,1,8,9}   8已经到达正确位置,下一轮不用在参与
	
	第三轮:
	第1次:arr[0]与arr[1]比较,3>2成立,就交换,{2,3,6,1,8,9}
	第2次:arr[1]与arr[2]比较,3>6不成立,不交换{2,3,6,1,8,9}
	第3次:arr[2]与arr[3]比较,6>1成立,就交换,{2,3,1,6,8,9}
	
	第三轮结果:{2,3,1,6,8,9}   6已经到达正确位置,下一轮不用在参与
	
	第四轮:
	第1次:arr[0]与arr[1]比较,2>3不成立,不交换{2,3,1,6,8,9} 
	第2次:arr[1]与arr[2]比较,3>1成立,就交换,{2,1,3,6,8,9} 
	
	第四轮结果:{2,1,3,6,8,9}    3已经到达正确位置,下一轮不用在参与
	
	第五轮
	第1次:arr[0]与arr[1]比较,2>1成立,就交换,{1,2,3,6,8,9}
	
	第五轮结果:{1,2,3,6,8,9}   2已经到达正确位置,下一轮不用在参与
	
	剩下1,肯定是最小的了,不用比较了
	
	6个元素,比较了5轮, n个元素需要n-1轮
	每一轮比较很多次
*/

示例代码:

public static void main(String[] args){
	int[] arr = {6,3,8,2,9,1};  //arr.length = 6
	
	//i=1,2,3,4,5  一共5轮
	for(int i=1; i<arr.length; i++){//轮数
		/*
		i=1,第1轮,j=0,1,2,3,4   arr[j]与arr[j+1]
		i=2,第2轮,j=0,1,2,3     arr[j]与arr[j+1]
		i=3,第3轮,j=0,1,2       arr[j]与arr[j+1]
		i=4,第4轮,j=0,1         arr[j]与arr[j+1]
		i=5,第5轮,j=0           arr[j]与arr[j+1]
		
		j=0, j<=arr.length-1-i
		*/
		for(int j=0; j<=arr.length-1-i; j++){
			if(arr[j] > arr[j+1]){
				int temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
	
	//结果
	for(int i=0; i<arr.length; i++){
		System.out.print(arr[i] + " ");
	}
}

示例代码:从大到小,从右到左

char[] arr = {'h','e','l','l','o','j','a','v','a'};
for(int i=1; i<arr.length; i++){//外循环的次数 = 轮数 = 数组的长度-1
    /*
    第1轮,i=1,从右到左两两比较,arr[8]与arr[7],arr[7]与arr[6]....arr[1]与arr[0]
    第2轮,i=2,从右到左两两比较,arr[8]与arr[7],arr[7]与arr[6]....arr[2]与arr[1]
    ...
    第8轮,i=8,从右到左两两比较,arr[8]与arr[7]
    		   arr[j]与arr[j-1]
    找两个关键点:(1)j的起始值:8(2)找j的终止值,依次是1,2,3,。。。8,得出j>=i
    */
    for(int j=8; j>=i; j--){
        //从大到小,后面的元素 > 前面的元素,就交换
        if(arr[j]>arr[j-1]){
            int temp = arr[j];
            arr[j] = arr[j-1];
            arr[j-1] = temp;
        }
    }
}	
		

练习

1、随机产生10个[0,100)之间整数,统计3的倍数的个数

2、随机产生10个[0,150)之间整数,统计既是3又是5,但不是7的倍数的个数

3、随机产生10个[0,100)之间整数,统计素数的个数

4、已知本组学员有:String[] names = {“张三”,”李四”,”王五”,”赵六”,”钱七”};,从键盘输入一个学生姓名,查看他是否是本组学员

5、声明两个数组,一个存储本组学员姓名,一个存储本组学员成绩,找出最高分同学的姓名

二维数组

  • 二维数组:本质上就是元素为一维数组的一个数组。

  • 二维数组的标记:[][]

int[][] arr; //arr是一个二维数组,可以看成元素是int[]一维数组类型的一个数组

二维数组也可以看成一个二维表,行*列组成的二维表,只不过这个二维表,每一行的列数还可能不同。但是每一个单元格中的元素的数据类型是一致的,例如:都是int,都是String等

1561524724397

二维数组的声明与初始化

语法格式:

//推荐
元素的数据类型[][] 二维数组的名称;

//不推荐
元素的数据类型  二维数组名[][];
//不推荐
元素的数据类型[]  二维数组名[];

面试:

int[] x, y[];
//x是一维数组,y是二维数组

静态初始化

元素的数据类型[][] 二维数组名 = new 元素的数据类型[][]{
			{元素1,元素2,元素3 。。。}, 
			{第二行的值列表},
			...
			{第n行的值列表}
		};

元素的数据类型[][] 二维数组名;
二维数组名 = new 元素的数据类型[][]{
			{元素1,元素2,元素3 。。。}, 
			{第二行的值列表},
			...
			{第n行的值列表}
		};
		
//以下格式要求声明与静态初始化必须一起完成
元素的数据类型[][] 二维数组的名称 = {
			{元素1,元素2,元素3 。。。}, 
			{第二行的值列表},
			...
			{第n行的值列表}
		};

如果是静态初始化,右边new 数据类型[][]中不能写数字,因为行数和列数,由{}的元素个数决定

举例:

int[][] arr;
arr = new int[][]{{1,2,3},{4,5,6},{7,8,9}};
arr = new int[3][3]{{1,2,3},{4,5,6},{7,8,9}};//错误,静态初始化右边new 数据类型[]中不能写数字

int[][] arr = new int[][]{{1,2,3},{4,5,6},{7,8,9}};

int[][] arr = {{1,2,3},{4,5,6},{7,8,9}};//声明与初始化必须在一句完成
public class Array2Demo1 {
	public static void main(String[] args) {
		//定义数组
		int[][] arr = {{1,2,3},{4,5},{6}};
		
		System.out.println(arr);
		System.out.println(arr[0]);
		System.out.println(arr[1]);
		System.out.println(arr[2]);
		
		System.out.println(arr[0][0]); //1
		System.out.println(arr[1][0]); //4
		System.out.println(arr[2][0]); //6
		
		System.out.println(arr[0][1]); //2
		System.out.println(arr[1][1]); //5
		//越界
		System.out.println(arr[2][1]); //错误
	}
}

动态初始化(规则二维表:每一行的列数是相同的)

//(1)确定行数和列数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
	m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行
	n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格

//此时创建完数组,行数、列数确定,而且元素也都有默认值

//(2)再为元素赋新值
二维数组名[行下标][列下标] =;
public static void main(String[] args) {
    //定义一个二维数组
    int[][] arr = new int[3][2];
    
    //定义了一个二维数组arr
    //这个二维数组有3个一维数组的元素
    //每一个一维数组有2个元素
    //输出二维数组名称
    System.out.println(arr); //地址值	[[I@175078b
    
    //输出二维数组的第一个元素一维数组的名称
    System.out.println(arr[0]); //地址值	[I@42552c
    System.out.println(arr[1]); //地址值	[I@e5bbd6
    System.out.println(arr[2]); //地址值	[I@8ee016
    
    //输出二维数组的元素
    System.out.println(arr[0][0]); //0
    System.out.println(arr[0][1]); //0
    
    //...
}	 

动态初始化(不规则:每一行的列数可能不一样)

//(1)先确定总行数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[总行数][];

//此时只是确定了总行数,每一行里面现在是null

//(2)再确定每一行的列数,创建每一行的一维数组
二维数组名[行下标] = new 元素的数据类型[该行的总列数];

//此时已经new完的行的元素就有默认值了,没有new的行还是null

//(3)再为元素赋值
二维数组名[行下标][列下标] =;
public static void main(String[] args) {
	//定义数组
	int[][] arr = new int[3][];
       
       System.out.println(arr);	//[[I@175078b
       
       System.out.println(arr[1][0]);//NullPointerException
	System.out.println(arr[0]); //null
	System.out.println(arr[1]); //null
	System.out.println(arr[2]); //null
	
	//动态的为每一个一维数组分配空间
	arr[0] = new int[2];
	arr[1] = new int[3];
	arr[2] = new int[1];
	
	System.out.println(arr[0]); //[I@42552c
	System.out.println(arr[1]); //[I@e5bbd6
	System.out.println(arr[2]); //[I@8ee016
	
	System.out.println(arr[0][0]); //0
	System.out.println(arr[0][1]); //0
	//ArrayIndexOutOfBoundsException
	//System.out.println(arr[0][2]); //错误
	
	arr[1][0] = 100;
	arr[1][2] = 200;
}

二维数组的相关名称及其表示方式

(1)二维数组的长度/行数:

​ 二维数组名.length

(2)二维数组的某一行:

​ 二维数组名[行下标]

​ 行下标的范围:[0, 二维数组名.length-1]

(3)某一行的列数:

​ 二维数组名[行下标].length

​ 因为二维数组的每一行是一个一维数组

(4)某一个元素

​ 二维数组名[行下标][列下标]

二维数组的遍历

for(int i=0; i<二维数组名.length; i++){
    for(int j=0; j<二维数组名[i].length; j++){
        System.out.print(二维数组名[i][j]);
    }
    System.out.println();
}

数组操作的常见异常

数组越界异常

观察一下代码,运行后会出现什么结果。

public static void main(String[] args) {
    int[] arr = {1,2,3};
    System.out.println(arr[3]);
}

创建数组,赋值3个元素,数组的索引就是0,1,2,没有3索引,因此我们不能访问数组中不存在的索引,程序运行后,将会抛出 ArrayIndexOutOfBoundsException 数组越界异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。

数组空指针异常

观察一下代码,运行后会出现什么结果。

public static void main(String[] args) {
	//定义数组
	int[][] arr = new int[3][];
       
       System.out.println(arr[0][0]);//NullPointerException
   }

因为此时数组的每一行还未分配具体存储元素的空间,此时arr[0]是null,此时访问arr[0][0]会抛出NullPointerException 空指针异常。

空指针异常在内存图中的表现

1572338767825

二维数组练习

练习1

1、请使用二维数组存储如下数据,并遍历显示

1

2 2

3 3 3

4 4 4 4

5 5 5 5 5

public static void main(String[] args){
	//1、声明一个二维数组,并且确定行数
	//因为每一行的列数不同,这里无法直接确定列数
	int[][]  arr = new int[5][];
	
	//2、确定每一行的列数
	for(int i=0; i<arr.length; i++){
		/*
		arr[0] 的列数是1
		arr[1] 的列数是2
		arr[2] 的列数是3
		arr[3] 的列数是4
		arr[4] 的列数是5
		*/
		arr[i] = new int[i+1];
	}
	
	//3、确定元素的值
	for(int i=0; i<arr.length; i++){
		for(int j=0; j<arr[i].length; j++){
			arr[i][j] = i+1;
		}
	}
	
	//4、遍历显示
	for(int i=0; i<arr.length; i++){
		for(int j=0; j<arr[i].length; j++){
			System.out.print(arr[i][j] + " ");
		}
		System.out.println();
	}
	
}
public static void main(String[] args){
	//1、声明一个二维数组,并且初始化
       int[][] arr = {
           {1},
           {2,2},
           {3,3,3},
           {4,4,4,4},
           {5,5,5,5,5}
       };
	
	
	//2、遍历显示
	for(int i=0; i<arr.length; i++){
		for(int j=0; j<arr[i].length; j++){
			System.out.print(arr[i][j] + " ");
		}
		System.out.println();
	}
	
}
public static void main(String[] args){
	//1、声明一个二维数组,并且确定行数
	//因为每一行的列数不同,这里无法直接确定列数
	int[][]  arr = new int[5][];
	
	
	for(int i=0; i<arr.length; i++){
		//2、确定每一行的列数
		arr[i] = new int[i+1];
		
		//3、确定元素的值
		for(int j=0; j<arr[i].length; j++){
			arr[i][j] = i+1;
			
			System.out.print(arr[i][j] + " ");
		}

		System.out.println();
	}

}

练习2

2、请使用二维数组存储如下数据,并遍历显示

1 1 1 1 1

2 2 2 2 2

3 3 3 3 3

4 4 4 4 4

public static void main(String[] args){
		int[][] arr = {
			{1,1,1,1,1},
			{2,2,2,2,2},
			{3,3,3,3,3},
			{4,4,4,4,4}
		};
		
		for(int i=0; i<arr.length; i++){
			for(int j=0; j<arr[i].length; j++){
				System.out.print(arr[i][j]+" ");
			}
			System.out.println();
		}
	}
public static void main(String[] args) {
	//1、声明二维数组,并确定行数和列数
	int[][] arr = new int[4][5];
	
	//2、确定元素的值
	for (int i = 0; i < arr.length; i++) {
		for (int j = 0; j < arr.length; j++) {
			arr[i][j] = i + 1;
		}
	}
	
	//3、遍历显示
	for(int i=0; i<arr.length; i++){
		for(int j=0; j<arr[i].length; j++){
			System.out.print(arr[i][j] + " ");
		}
		System.out.println();
	}
}

练习3

3、请使用二维数组存储如下数据,并遍历显示

String[][] employees = {
        {"10", "1", "段誉", "22", "3000"},
        {"13", "2", "令狐冲", "32", "18000", "15000", "2000"},
        {"11", "3", "任我行", "23", "7000"},
        {"11", "4", "张三丰", "24", "7300"},
        {"12", "5", "周芷若", "28", "10000", "5000"},
        {"11", "6", "赵敏", "22", "6800"},
        {"12", "7", "张无忌", "29", "10800","5200"},
        {"13", "8", "韦小宝", "30", "19800", "15000", "2500"},
        {"12", "9", "杨过", "26", "9800", "5500"},
        {"11", "10", "小龙女", "21", "6600"},
        {"11", "11", "郭靖", "25", "7100"},
        {"12", "12", "黄蓉", "27", "9600", "4800"}
    };

其中”10”代表普通职员,”11”代表程序员,”12”代表设计师,”13”代表架构师

1561529559251

public static void main(String[] args) {
	String[][] employees = {
	        {"10", "1", "段誉", "22", "3000"},
	        {"13", "2", "令狐冲", "32", "18000", "15000", "2000"},
	        {"11", "3", "任我行", "23", "7000"},
	        {"11", "4", "张三丰", "24", "7300"},
	        {"12", "5", "周芷若", "28", "10000", "5000"},
	        {"11", "6", "赵敏", "22", "6800"},
	        {"12", "7", "张无忌", "29", "10800","5200"},
	        {"13", "8", "韦小宝", "30", "19800", "15000", "2500"},
	        {"12", "9", "杨过", "26", "9800", "5500"},
	        {"11", "10", "小龙女", "21", "6600"},
	        {"11", "11", "郭靖", "25", "7100"},
	        {"12", "12", "黄蓉", "27", "9600", "4800"}
	    };
	System.out.println("员工类型\t编号\t姓名\t年龄\t薪资\t奖金\t股票\t");
	for (int i = 0; i < employees.length; i++) {
		switch(employees[i][0]){
		case "10":
			System.out.print("普通职员");
			break;
		case "11":
			System.out.print("程序员");
			break;
		case "12":
			System.out.print("设计师");
			break;
		case "13":
			System.out.print("架构师");
			break;
		}
		for (int j = 1; j < employees[i].length; j++) {
			System.out.print("\t" + employees[i][j]);
		}
		System.out.println();
	}
}

二维数组的内存图分析

示例一

int[][] arr = {
    {1},
    {2,2},
    {3,3,3},
    {4,4,4,4},
    {5,5,5,5,5}
};

1562112672215

示例二

//1、声明二维数组,并确定行数和列数
int[][] arr = new int[4][5];

//2、确定元素的值
for (int i = 0; i < arr.length; i++) {
	for (int j = 0; j < arr.length; j++) {
		arr[i][j] = i + 1;
	}
}

1562113179785

示例三

//1、声明一个二维数组,并且确定行数
//因为每一行的列数不同,这里无法直接确定列数
int[][]  arr = new int[5][];

//2、确定每一行的列数
for(int i=0; i<arr.length; i++){
	/*
	arr[0] 的列数是1
	arr[1] 的列数是2
	arr[2] 的列数是3
	arr[3] 的列数是4
	arr[4] 的列数是5
	*/
	arr[i] = new int[i+1];
}

//3、确定元素的值
for(int i=0; i<arr.length; i++){
	for(int j=0; j<arr[i].length; j++){
		arr[i][j] = i+1;
	}
}

1562113981079

面向对象基础(上)

面向对象思想概述

概述

Java语言是一种面向对象的程序设计语言,而面向对象思想(OOP)是一种程序设计思想,我们在面向对象思想的指引下,使用Java语言去设计、开发计算机程序。
这里的对象泛指现实中一切事物,每种事物都具备自己的属性行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。
它区别于面向过程思想(POP),强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。

面向对象与面向过程的区别

面向过程:POP: Process-Oriented Programming

​ 以函数(方法)为最小单位

​ 数据独立于函数之外

​ 以过程,步骤为主,考虑怎么做

面向对象:OOP: Object Oriented Programming

​ 以类/对象为最小单位,类包括:数据+方法

​ 以对象(谁)为主,考虑谁来做,谁能做

面向对象仍然包含面向过程,只不过关注点变了,关注谁来做

程序员的角色:

面向过程:程序员是具体执行者

面向对象:程序员是指挥者

面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。

例子:把大象装进冰箱

1561535567825

3、面向对象的基本特征

面向对象的语言中,包含了三大基本特征,即封装、继承和多态。

类和对象

环顾周围,你会发现很多对象,比如桌子,椅子,同学,老师等。桌椅属于办公用品,师生都是人类。那么什么是类呢?什么是对象呢?

什么是类

  • :是一类具有相同特性的事物的抽象描述,是一组相关属性行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。

  • 类可以看做是一个模版,或者图纸,系统根据类的定义来造出对象。我们要造一个汽车,怎么样造?类就是这个图纸,规定了汽车的详细信息,然后根据图纸将汽车造出来。

    类:我们叫做class。 对象:我们叫做Object,instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。

***示例1:***

***英雄联盟、王者荣耀中的类和对象***

![1597040261305](https://note-1259153703.cos.ap-nanjing.myqcloud.com/images/20210825234938.jpg)

英雄就是类,具体的英雄,盖伦、提莫是对象。

***示例2:***

***月饼模具和月饼***

月饼模具是类,使用月饼模具制作的一个个月饼就是对象

![1597040368607](https://note-1259153703.cos.ap-nanjing.myqcloud.com/images/20210825234943.jpg)

现实中,描述一类事物:

  • 属性:就是该事物的状态信息。
  • 行为:就是该事物能够做什么。

举例:小猫。

​ 属性:名字、体重、年龄、颜色。
​ 行为:走、跑、叫。

什么是对象

  • 对象:是一类事物的具体体现。对象是类的一个实例(对象并不是找个女朋友),必然具备该类事物的属性和行为。

现实中,一类事物的一个实例:一只小猫 。

举例:一只小猫。

​ 属性:tom、5kg、2 years、yellow。
​ 行为:溜墙根走、蹦跶的跑、喵喵叫。

类与对象的关系

  • 类是对一类事物的描述,是抽象的
  • 对象是一类事物的实例,是具体的
  • 类是对象的模板,对象是类的实体

类的定义和对象的创建

事物与类的对比

现实世界的一类事物:

属性:事物的状态信息。
行为:事物能够做什么。

Java中用class描述事物也是如此:

成员变量:对应事物的属性
成员方法:对应事物的行为

类的定义格式

public class ClassName {
  //成员变量
  //成员方法 
}
  • 定义类:就是定义类的成员,包括成员变量成员方法
  • 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外
  • 成员方法:和以前写的main方法格式类似。只不过功能和形式更丰富了。在类中,方法外。

类的定义格式举例:

public class Person {
  	//成员变量
  	String name;//姓名
    int age;//年龄
    boolean isMarried;
    
    public void walk(){
        System.out.println("人走路...");
    }
    public String display(){
        return "名字是:" + name + ",年龄是:" + age + ",Married:" + isMarried;
    }
}

对象的创建

创建对象:

new 类名()//也称为匿名对象

//给创建的对象命名
//或者说,把创建的对象用一个引用数据类型的变量保存起来
类名 对象名 = new 类名();

类似于:

System.out.println("高老师年龄是:" + 18);//如果确定只在这里一次性使用,那么可以不用变量保存(#^.^#)

//把18用int类型的age变量保存起来,方便后面使用
int age = 18;
System.out.println("高老师年龄是:" + age);
System.out.println("仓老师比高老师大10岁,年龄是:" + (age+10));

那么,对象名中存储的是什么呢?答:对象地址

class Student{
    
}
public class TestStudent{
    //Java程序的入口
    public static void main(String[] args){
        System.out.println(new Student());//Student@7852e922

        Student stu = new Student();
        System.out.println(stu);//Student@4e25154f
        
        int[] arr = new int[5];
		System.out.println(arr);//[I@70dea4e
    }
}
//Student和TestStudent没有位置要求,谁在上面谁在下面都可以
//但是如果TestStudent类的main中使用了Student类,那么要求编译时,这个Student已经写好了,不写是不行的
//如果两个类都在一个.java源文件中,只能有一个类是public的

发现学生对象和数组对象类似,直接打印对象名和数组名都是显示“类型@对象的hashCode值”,所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。

那么像“Student@4e25154f”是对象的地址吗?不是,因为Java是对程序员隐藏内存地址的,不暴露内存地址信息,所以打印对象时不直接显示内存地址,而是JVM提取了对象描述信息给你现在,默认提取的是对象的运行时类型@代表对象唯一编码的hashCode值。

1561597909862

包(Package)

包的作用

(1)可以避免类重名:有了包之后,类的全名称就变为:包.类名

(2)分类组织管理众多的类

例如:

  • java.lang——包含一些Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能
  • java.net——包含执行与网络相关的操作的类和接口。
  • java.io ——包含能提供多种输入/输出功能的类。
  • java.util——包含一些实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random。
  • java.text——包含了一些java格式化相关的类
  • java.sql和javax.sql——包含了java进行JDBC数据库编程的相关类/接口
  • java.awt和java.swing——包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。

(3)可以控制某些类型或成员的可见范围

如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用

声明包的语法格式

package 包名;

注意:

(1)必须在源文件的代码首行

(2)一个源文件只能有一个声明包的语句

包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置

例如:com.atguigu.xxx;

建议大家取包名时不要使用“java.xx”包

如何跨包使用类

前提:被使用的类或成员的权限修饰符是>缺省的,即可见的

(1)使用类型的全名称

例如:java.util.Scanner input = new java.util.Scanner(System.in);

(2)使用import 语句之后,代码中使用简名称

import语句告诉编译器到哪里去寻找类。

import语句的语法格式:

import.类名;
import.*;
import static.类名.静态成员; //后面再讲

注意:

使用java.lang包下的类,不需要import语句,就直接可以使用简名称

import语句必须在package下面,class的上面

当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称

示例代码:

package com.atguigu.bean;

public class Student {
	// 成员变量
	private String name;
	private int age;

	// 构造方法
	public Student() {
	}

	public Student(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 setAge(int age) {
		this.age = age;
	}

	public int getAge() {
		return age;
	}
}
package com.atguigu.test;

import java.util.Scanner;
import java.util.Date;
import com.atguigu.bean.Student;

public class Test{
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        Student stu = new Student();
        String str = "hello";
        
        Date now = new Date();
        java.sql.Date d = new java.sql.Date(346724566);        
    }
}

成员变量

成员变量的分类

实例变量:没有static修饰,也叫对象属性,属于某个对象的,通过对象来使用

类变量:有static修饰,也叫类变量,属于整个类的,不是属于某个实例

如何声明成员变量?

【修饰符】 class 类名{
    【修饰符】 数据类型  属性名;    //属性有默认值
    【修饰符】 数据类型  属性名 =; //属性有初始值
}

说明:属性的类型可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)

例如:声明一个中国人的类

class Chinese{
	static String country;
	String name;
    char gender = '男';//显式赋值
}

如何在类外面访问成员变量?

类变量

类名.静态成员变量  //推荐

对象名.静态成员变量 //不推荐

实例变量

对象名.静态成员变量  //只能使用这种方式

例如:

public class TestChinese {
	public static void main(String[] args) {
		//类名.静态成员变量
		System.out.println(Chinese.country);
		//错误,非静态成员变量必须通过对象.进行访问
//		System.out.println(Chinese.name);
		
		Chinese c1 = new Chinese();
		//对象名.非静态成员变量
		System.out.println(c1.name);
		//静态的成员变量也可以通过对象.进行访问
		//对象名.非静态成员变量
		System.out.println(c1.country);
        System.out.println(c1.gender);
	}
}
class Chinese{
	static String country;
	String name;
    char gender = '男';
}

成员变量的特点

成员变量有默认值

基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
数据类型 默认值
引用类型 数组,类,接口 null

类变量的值是所有对象共享的,而实例变量的值是每个对象独立的

public class TestChinese {
	public static void main(String[] args) {
		Chinese c1 = new Chinese();
		Chinese c2 = new Chinese();
		
		c1.name = "张三";
		c2.name = "李四";
        c2.gender = '女';
		
//		c1.country = "中国";
		Chinese.country = "中国";//推荐
		
		System.out.println("c1.country = " + c1.country + ",c1.name = " + c1.name + ",c1.gender = " + c1.gender);
		System.out.println("c2.country = " + c2.country + ",c2.name = " + c2.name + ",c2.gender = " + c2.gender);
	}	
}
class Chinese{
	static String country;
	String name;
    char gender = '男';
}

成员变量的内存图

​ 内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。Java虚拟机要运行程序,必须要对内存进行空间的分配和管理,每一片区域都有特定的处理数据方式和内存管理方式。

1561465258546

区域名称 作用
程序计数器 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址
本地方法栈 当程序中调用了native的本地方法时,本地方法执行期间的内存区域
方法区 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
堆内存 存储对象(包括数组对象),new来创建的,都存储在堆内存。
虚拟机栈 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。
class Test08FieldSave{
	public static void main(String[] args){
		Chinese c1 = new Chinese();
		c1.name = "张三";
		System.out.println(c1.country);//静态变量,也可以使用"对象名."进行访问
		System.out.println(c1.name);//非静态的实例变量通过"对象名."进行访问
		
		
		Chinese c2 = new Chinese();
		c2.name = "李四";
		System.out.println(c2.country);
		System.out.println(c2.name);
		
		System.out.println("--------------------------------------");
		//其中一个对象将静态变量的值修改了,其他对象都会改变
		//因为静态变量只存一份
		c1.country = "中华人民共和国";
		System.out.println(c1.country);
		System.out.println(c2.country);
		System.out.println(Chinese.country);
		
		
		//其中一个对象将非静态实例变量修改了,其他对象不受影响
		c1.name = "张三丰";
		System.out.println(c1.name);
		System.out.println(c2.name);
	}
	
}

class Chinese{
	static String country = "中国";//静态变量,所有中国人的国家的名称是一样,只需要存储一份
	String name;//实例变量,每一个中国人的姓名是独立,每一个对象单独存储
}

class MyDate{
	int year;
	int month;
	int day;
}
class Employee{
	String name;
	MyDate birthday;
}
class Test09FieldExer3{
	public static void main(String[] args){
		//创建两个员工对象
		Employee e1 = new Employee();
		Employee e2 = new Employee();
		
		//为两个员工对象的成员变量赋值
		e1.name = "张三";
		e1.birthday = new MyDate();
		
		e2.name = "李四";
		e2.birthday = new MyDate();
		
		e1.birthday.year = 2000;
		e1.birthday.month = 1;
		e1.birthday.day = 1;
		
		e2.birthday.year = 2000;
		e2.birthday.month = 3;
		e2.birthday.day = 8;
		
		System.out.println("第一个员工,姓名:" + e1.name +",生日:" + e1.birthday.year + "年" + e1.birthday.month + "月" + e1.birthday.day + "日");
		System.out.println("第二个员工,姓名:" + e2.name +",生日:" + e2.birthday.year + "年" + e2.birthday.month + "月" + e2.birthday.day + "日");
	}
}

成员变量练习题

(1)声明一个圆的图形类,有属性:半径
在测试类的main中,创建圆的2个对象,为半径属性赋值,并显示两个圆的半径值和面积值
提示:圆周率为Math.PI

(2)声明一个银行账户类,有属性:利率、账号、余额

​ 在测试类的main中,创建账户类的两个对象,其中所有账户的利率是相同的,都是0.035,而账号和余额是不同的,并打印显示

(3)声明一个MyDate类型,有属性:年,月,日

​ 声明另一个Employee类型,有属性:姓名(String类型),生日(MyDate类型)

在测试类中的main中,创建两个员工对象,并为他们的姓名和生日赋值,并显示

成员方法

成员变量是用来存储对象的数据信息的,那么如何表示对象的行为功能呢?就要通过方法来实现

方法的概念

方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元。

把一个功能封装为方法的目的是,可以实现代码重用,从而简少代码量。

方法的原则

方法的使用原则:

(1)必须先声明后使用

类,变量,方法等都要先声明后使用

(2)不调用不执行,调用一次执行一次。

成员方法的分类

成员方法分为两类:

  • 实例方法:没有static修饰的方法,必须通过实例对象来调用。
  • 静态方法:有static修饰的方法,也叫类方法,可以由类名来调用。

如何声明方法

1、方法声明的位置必须在类中方法外

2、语法格式

【修饰符】 返回值类型 方法名(【参数列表:参数类型1 参数名1,参数类型2 参数名, ......){
        方法体;
        【return 返回值;}
  • 修饰符: 修饰符后面一一介绍,例如:public,static等都是修饰符
  • 返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者
    • 基本数据类型
    • 引用数据类型
    • 无返回值类型:void
  • 方法名:给方法起一个名字,见名知意,能准确代表该方法功能的名字
  • 参数列表:方法内部需要用到其他方法中的数据,需要通过参数传递的形式将数据传递过来,可以是基本数据类型、引用数据类型、也可以没有参数,什么都不写
  • 方法体:特定功能代码
  • return:结束方法,并将方法的结果返回去,
    • 如果返回值类型不是void,方法体中必须保证一定有return 返回值;语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
    • 如果返回值类型为void时,return 后面不用跟返回值,甚至也可以没有return语句。
    • return语句后面就不能再写其他代码了,否则会报错:Unreachable code

声明位置示例:

{
    方法1(){
        
    }
    方法2(){
        
    }
}

错误示例:

{
    方法1(){
        方法2(){  //位置错误
        
   		}
    }
}

示例一:

声明一个圆的图形类:

​ 属性(成员变量):半径,

​ 成员方法:求面积的方法,返回圆对象信息的方法

​ 在测试类的main中,创建圆的2个对象,为半径属性赋值,调用两个方法进行测试
​ 提示:圆周率为Math.PI

class Circle{
	double radius;
	double area() {
		return Math.PI * radius * radius;
	}
}

Circle不同的对象,半径值不同,那么面积也不同,所以这里area()是非静态的

示例二:

声明一个计算工具类CountTools:

​ 方法1:求两个整数的最大值

class CountTools{
	static int max(int a, int b) {
        return a > b ? a : b;
	}
}

CountTools只是一个工具类,求两个整数最大值的功能,和CountTools对象无关,所以这里max方法声明为静态的更好,当然也可以声明为非静态的,就是调用的时候需要创建CountTools对象而已。

如何在其他类中调用方法

实例方法

对象名.实例方法(【实参列表】)  //必须通过对象来访问

示例代码:

public class TestCircle {
	public static void main(String[] args) {
		Circle c1 = new Circle();
		c1.radius = 1.2;
		System.out.println("c1的面积:" + c1.area());
		//非静态方法只能通过"对象."进行访问
//		System.out.println("c1的面积:" + Circle.area());
        
		Circle c2 = new Circle();
		c2.radius = 2.5;
		System.out.println("c2的面积:" + c2.area());
	}
}
class Circle{
	double radius;
	public double area() {
		return Math.PI * radius * radius;
	}
}

类方法

类名.类方法(【实参列表】)  //推荐

对象名.类方法(【实参列表】) //不推荐

示例:

public class TestCount {
	public static void main(String[] args) {
		System.out.println(CountTools.max(4, 1));
		
		//静态方法也可以通过“对象.”访问,就是麻烦点
		CountTools c = new CountTools();
		System.out.println(c.max(2, 5));
	}
}
class CountTools{
	static int max(int a, int b) {
		return a > b ? a : b;
	}
}

总结

  • 形参:在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
  • 实参:调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。

总结:

(1)调用时,需要传“实参”,实参的个数、类型、顺序顺序要与形参列表一一对应

​ 如果方法没有形参,就不需要也不能传实参。

(2)调用时,如果方法有返回值,可以接受或处理返回值结果,当然也可以不接收,那么此时返回值就丢失了。

​ 如果方法的返回值类型是void,不需要也不能接收和处理返回值结果。

在本类中访问本类的成员变量和成员方法

直接用,不需要加“对象名.”和”类名.”

唯一例外:静态方法中不能直接访问本类的非静态的成员变量和成员方法

class Circle{
	double radius;
	
	//写一个方法,可以返回“圆对象”的详细信息
	String getDetailInfo(){
		return "半径:" + radius + ",面积:" + area() +",周长:" + perimeter();
	}
	
	//写一个方法,可以返回“圆对象”的面积
	double area(){
		return Math.PI*radius*radius;
	}
	
	//写一个方法,可以返回“圆对象”的周长
	double perimeter(){
		return 2*Math.PI*radius;
	}

}
class Test{
		
	static void test(){
		System.out.println("");
	}
	void method(){
		 test();
	}
    
    public static void main(String[] args){
        method();//错误
        test();//正确
    }
}

方法的声明与调用练习

1、声明数学工具类MathTools

(1)静态方法1:可以比较两个整数是否相同
(2)静态方法2:可以判断某个数是否是素数
(3)静态方法3:可以返回某个整数所有的约数(约数:从1到这个数之间所有能把它整除的数)
在Test测试类的main中调用测试

2、声明数组工具类ArraysTools

(1)静态方法1:可以实现给任意整型数组实现从小到大排序
(2)静态方法2:可以遍历任意整型数组,返回结果效果:[元素1,元素2,元素3。。。]

3、声明矩形类

(1)包含属性:长、宽

(2)包含3个方法:

​ 求面积、

​ 求周长、

​ 返回矩形对象的信息:长:xx,宽:xx,面积:xx,周长:xx

4、声明一个圆类,有半径radius成员变量

​ 声明一个图形工具类GraphicTools,包含一个静态方法可以返回两个圆中面积大的那一个圆的方法

​ 在测试类中测试

方法调用内存分析

方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。

栈结构:先进后出,后进先出。

示例一:

public class TestCount {
	public static void main(String[] args) {
        int a = 4;
        int b = 2;
		int m = CountTools.max(a, b));
	}
}
class CountTools{
	static int max(int a, int b) {
		return a > b ? a : b;
	}
}

1572349992849

示例二:

public class TestCircle {
	public static void main(String[] args) {
		Circle c1 = new Circle();
		c1.radius = 1.2;
		int area1 = c1.area();
		
		Circle c2 = new Circle();
		c2.radius = 2.5;
		int area2 = c2.area();
	}
}
class Circle{
	double radius;
	public double area() {
		return Math.PI * radius * radius;
	}
}

1572350522409

示例三:

public class Test {
	public static void main(String[] args) {
		int[] arr = {2,4,1,5,3};
		
		ArrayUtil.sort(arr);
		
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}
class ArrayUtil{
	public static void sort(int[] arr){
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length - i; j++) {
				if(arr[j] > arr[j+1]){
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
	}
}

1572350909017

方法的参数传递机制

  • 方法的参数传递机制:实参给形参赋值
    • 方法的形参是基本数据类型时,形参值的改变不会影响实参;
    • 方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
      • 注意:String、Integer等特殊类型容易错

示例代码1:

class Test{
    public static void swap(int a, int b){
        int temp = a;
        a = b;
        b = temp;
	}

	public static void main(String[] args){
        int x = 1;
        int y = 2;
        swap(x,y);//调用完之后,x与y的值不变
    }
}

示例代码2:

class Test{
    public static void change(MyData my){
        my.num *= 2;
    }
    
    public static void main(String[] args){
        MyData m = new MyData();
        m.num = 1;
        
        change(m);//调用完之后,m对象的num属性值就变为2
    }
}

class MyData{
    int num;
}

示例代码3:

public class Test {
	public static void main(String[] args) {
		int[] arr = {2,4,1,5,3};
		
		ArrayUtil.sort(arr);
		
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}
class ArrayUtil{
	public static void sort(int[] arr){
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length - i; j++) {
				if(arr[j] > arr[j+1]){
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
	}
}

陷阱1:

/*
陷阱1:在方法中,形参 = 新new对象,那么就和实参无关了
*/
class Test{
    public static void change(MyData my){
        my = new MyData();//形参指向了新对象
        my.num *= 2;
    }
    
    public static void main(String[] args){
        MyData m = new MyData();
        m.num = 1;
        
        change(m);//调用完之后,m对象的num属性值仍然为1
    }
}

class MyData{
    int num;
}

陷阱2:见字符串和包装类部分

public class Test {
	public static void main(String[] args) {
		StringUtil util = new StringUtil();
		String str = "尚硅谷";
		util.change(str);
		System.out.println(str);
	}
}
class StringUtil{
	public void change(String str){
		str += "你好";//String对象不可变,一旦修改就会产生新对象
	}
}

成员变量与局部变量的区别

1、变量的分类

  • 成员变量

    • 静态变量
    • 实例变量
  • 局部变量

2、区别

1、声明位置和方式
(1)静态变量:在类中方法外,并且有static修饰
(2)实例变量:在类中方法外,没有static修饰
(3)局部变量:在方法体{}中或方法的形参列表、代码块中

2、在内存中存储的位置不同
(1)静态变量:方法区
(2)实例变量:堆
(3)局部变量:栈

3、生命周期
(1)静态变量:和类的生命周期一样,因为它的值是该类所有对象共享的,早于对象的创建而存在。
(2)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
而且每一个对象的实例变量是独立的。
(3)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
而且每一次方法调用都是独立。

4、作用域
(1)静态变量和实例变量:不谈作用域
在本类中,唯一的限制,静态方法或静态代码块中不能使用非静态的,其他都可以直接使用。
在其他类中,能不能使用看修饰符(public,protected,private等)
(2)局部变量:有作用域
出了作用域就不能使用

5、修饰符(后面来讲)
(1)静态变量:很多
public,protected,private,final,volatile等,一定有的是static
(2)实例变量
public,protected,private,final,volatile,transient等
(3)局部变量
final

public,protected,private:权限修饰符
final:是否是常量,即值是否可以修改
volatile:和多线程有关
transient:是否序列化,和IO有关

6、默认值
(1)静态变量:有默认值
(2)实例变量:有默认值
(3)局部变量:没有,必须初始化
其中的形参比较特殊,靠实参给它初始化。

可变参数

JDK1.5之后,如果我们定义一个方法时,此时某个形参的类型可以确定,但是形参的个数不确定,那么我们可以使用可变参数。

格式:

【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){  }

要求:

(1)一个方法最多只能有一个可变参数

(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个

【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型[] 形参名){  }

只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。

求n个整数的和

public class ChangeArgs {
	public static void main(String[] args) {
		int[] arr = { 1, 4, 62, 431, 2 };
		int sum1 = getSum1(arr);
		System.out.println(sum1);

		int sum2 = getSum2(arr);
		System.out.println(sum2);
		int sum3 = getSum2(1, 4, 62, 431, 2);
		System.out.println(sum3);
	}

	// 完成数组 所有元素的求和
	// 原始写法
	public static int getSum1(int[] arr) {
		int sum = 0;
		for (int i = 0; i < arr.length; i++) {
			sum += arr[i];
		}

		return sum;
	}

	// 可变参数写法
	public static int getSum2(int... arr) {
		int sum = 0;
		for (int i = 0; i < arr.length; i++) {
			sum += arr[i];
		}
		return sum;
	}
}

求1-n个整数中的最大值

public class ChangeArgs_Exer1 {
	public static void main(String[] args) {
		System.out.println(max(1));
		System.out.println(max(5,3,2,6));
	}

	public static int max(int num, int... others){
		int max = num;
		for (int i = 0; i < others.length; i++) {
			if(max < others[i]){
				max = num;
			}
		}
		return max;
	}
}

字符串拼接

需求一:返回n个字符串拼接结果,如果没有传入字符串,那么返回空字符串””

public class ChangeArgs_Exer2 {
	public static void main(String[] args) {
		System.out.println(concat());
		System.out.println(concat("hello","world"));
	}
	public static String concat(String... args){
		String str = "";
		for (int i = 0; i < args.length; i++) {
			str += args[i];
		}
		return str;
	}
}

需求二:n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串””

public class ChangeArgs_Exer4 {
	public static void main(String[] args) {
		System.out.println(concat('+'));
		System.out.println(concat('+',"hello","world"));
	}
	public static String concat(char seperator, String... args){
		String str = "";
		for (int i = 0; i < args.length; i++) {
			if(i==0){
				str += args[i];
			}else{
				str += seperator + args[i];
			}
		}
		return str;
	}
}

课后练习

1、声明一个方法,可以找出任意个整数的最大公约数

2、声明一个方法,可以找出任意个字符串中的公共字符,例如:hello与world的公共字符是o和l,如果没有就返回””

提示:获取字符串长度的方法:int length()

​ 例如:字符串.length(), “hello”.length()返回5

​ 获取字符串[index]位置的字符:char charAt(int index)

​ 例如:字符串.charAt(index) hello.charAt(1)返回’e’

参考答案:

class Test12MethodExer1{
	public static void main(String[] args){
		System.out.println(maxYue(6,9));
		System.out.println(maxYue(16,18,4,8));
	}
	
	public static int maxYue(int... args){
		//找很多个数的公约数
		//(1)找出它们中最小的
		//类似于在数组中找最小值
		int min = args[0];
		for(int i=1; i<args.length; i++){
			if(args[i] < min){
				min = args[i];
			}
		}
		
		//(2)从小的数往1的方向找,找到的第一个公约数就是它们的最大公约数
		for(int i=min; i>=1; i--){
			//这个i得把args中所有的数都整除了,那么i就是他们的公约数
			boolean flag = true;//假设i可以把args中所有数都整除了
			for(int j=0; j<args.length; j++){
				if(args[j] % i !=0){//args中有一个数不能被i整除,说明这个i不是它们的公约数
					flag = false;
					break;
				}
			}
			if(flag){
				return i;//return会结束当前方法
			}
		}
		
		return 1;//1是所有数的公约数
		/*
		假设args中6和9  args[0]是6,args[1]是9,min=6
		外循环第一次i=min=6,  
				内循环第一次:j=0,  if(args[0] % 6!=0)不成立 j++
				内循环第二次:j=1,  if(args[1] % 6!=0)成立   flag = false  ;break;
				说明i不是它们公约数
		外循环第二次i=5
				内循环第一次:j=0,  if(args[0] % 5 !=0)成立 j++  flag = false;break;
				说明i不是它们公约数
		外循环第三次i=4
				内循环第一次:j=0,  if(args[0] % 4 !=0)成立 j++  flag = false;break;
				说明i不是它们公约数
		外循环第四次i=3
				内循环第一次:j=0,  if(args[0] % 3 !=0)不成立 j++ 
				内循环第二次:j=1,  if(args[1] % 3 !=0)不成立 j++  
				if(flag)成立,return i;
		*/
	}
}
public static void main(String[] args) {
        // 声明一个方法,可以找出任意个整数的最大公约数
        int maxYueShu = getMaxYueShu(10, 20, 15, 20);
        System.out.println("maxYueShu = " + maxYueShu);
        System.out.println(sameChars("hello", "world"));//ol
        System.out.println(sameChars("chai", "wolrd"));
        System.out.println(sameChars("atguigu", "java"));
        System.out.println(sameChars("samewordsg", "string", "words"));
    }

    public static String sameChars(String... words) {
        String same = "";
        for (char a = 'A'; a <= 'z'; a++) {
            //统计相同单词的个数
            int count = 0;
            for (int j = 0; j < words.length; j++) {
                for (int h = 0; h < words[j].length(); h++) {
                    if (words[j].charAt(h) == a) {
                        count++;
                        break;
                    }
                }
            }
            //当相同的个数等于字符个数时 判定所有的字符串都出现过
            if (count == words.length) {
                same += a;
            }
        }
        return same;
    }

方法重载

  • 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
  • 参数列表:数据类型个数不同,数据类型不同,数据类型顺序不同。
  • 重载方法调用:JVM通过方法的参数列表,调用不同的方法。

比较两个数据是否相等

比较两个数据是否相等。参数类型分别为两个byte类型,两个short类型,两个int类型,两个long类型,并在main方法中进行测试。

public class Method_Demo6 {
    public static void main(String[] args) {
        //定义不同数据类型的变量
        byte a = 10;
        byte b = 20;
        short c = 10;
        short d = 20;
        int e = 10;
        int f = 10;
        long g = 10;
        long h = 20;
        // 调用
        System.out.println(compare(a, b));
        System.out.println(compare(c, d));
        System.out.println(compare(e, f));
        System.out.println(compare(g, h));
    }
    // 两个byte类型的
    public static boolean compare(byte a, byte b) {
        System.out.println("byte");
        return a == b;
    }

    // 两个short类型的
    public static boolean compare(short a, short b) {
        System.out.println("short");
        return a == b;
    }

    // 两个int类型的
    public static boolean compare(int a, int b) {
        System.out.println("int");
        return a == b;
    }

    // 两个long类型的
    public static boolean compare(long a, long b) {
        System.out.println("long");
        return a == b;
    }
}

求各种最大值

用重载实现:
定义方法求两个整数的最大值
定义方法求三个整数的最大值
定义方法求两个小数的最大值

//求两个整数的最大值
public int max(int a,int b){
    return a>b?a:b;
}
	
//求三个整数的最大值
public int max(int a, int b, int c){
    return max(max(a,b),c);
}
	
//求两个小数的最大值
public double max(double a, double b){
    return a>b?a:b;
}

判断两个方法是否是合理的重载方法

//判断如下两个方法是否构成重载:是
class StringUtil{
	public static String concat(char seperator, String... args){
		String str = "";
		for (int i = 0; i < args.length; i++) {
			if(i==0){
				str += args[i];
			}else{
				str += seperator + args[i];
			}
		}
		return str;
	}
	public static String concat(String[] args){
		String str = "";
		for (int i = 0; i < args.length; i++) {
			str += args[i];
		}
		return str;
	}
}
//判断如下两个方法是否构成重载:不是
class Count{
	public static int getSum(int... nums){
		int sum = 0;
		for (int i = 0; i < nums.length; i++) {
			sum += nums[i];
		}
		return sum;
	}
	public static int getSum(int[] nums){
		int sum = 0;
		for (int i = 0; i < nums.length; i++) {
			sum += nums[i];
		}
		return sum;
	}
}
class Test06_Overload_Problem2{
	public static void main(String[] args){
		System.out.println(sum(1,2));//(int a, int b)
		System.out.println(sum(1,2,3));//(int... args)和(int a, int... args)都兼容,就有问题
	}

	//不调用编译没问题,但是调用时就有问题
	public static int sum(int a, int b){
		return a+b;
	}
	public static int sum(int... args){
		int sum = 0;
		for(int i=0; i<args.length; i++){
			sum += args[i];
		}
		return sum;
	}
	public static int sum(int a, int... args){
		int sum = a;
		for(int i=0; i<args.length; i++){
			sum += args[i];
		}
		return sum;
	}	
}

课后练习

1、声明一个数组工具类ArraysTools,包含几个重载方法

(1)重载方法系列1:可以为byte[],short[],int[],long[],double[],char[]数组实现从小到大排序

(2)重载方法系列2:可以遍历byte[],short[],int[],long[],double[],char[]数组,遍历结果形式:

​ [元素1,元素2,。。。]

2、声明一个图形工具类GraphicTools,包含两个重载方法

(1)包含方法1:根据底边和高,求三角形面积,
(2)包含方法2:根据三条边,求三角形面积

提示:根据三角形三边求面积的海伦公式:

1597044718215

命令行参数(了解)

通过命令行给main方法的形参传递的实参称为命令行参数

1561632816552

public class TestCommandParam{
	//形参:String[] args
	public static void main(String[] args){
		System.out.println(args);
		System.out.println(args.length);
		
		for(int i=0; i<args.length; i++){
			System.out.println("第" + (i+1) + "个参数的值是:" + args[i]);
		}
	}
}

运行命令:

java TestCommandParam
java TestCommandParam 1 2 3
java TestCommandParam hello atguigu

static关键字

static是一个修饰符,可以修饰:

  • 成员变量,我们称为类变量,或静态变量,表示某个类的所有对象共享的数据
  • 成员方法,我们称为类方法,或静态方法,表示不需要实例对象就可以调用的方法,使用“类名.”进行调用
    • 父类的静态方法可以被继承不能被重写
    • 父接口的静态方法不能被实现类继承
  • 代码块,我们称为静态代码块,或静态初始化块,用于为静态变量初始化,每一个类的静态代码块只会执行一次,在类第一次初始化时执行
  • 成员内部类,我们称为静态成员内部类,简称静态内部类,不需要外部类实例对象就可以使用的内部类,在静态内部类中只能使用外部类的静态成员
    • static不能修饰top-level的类
  • 静态导入
import static.类名.静态成员;
import static.类名.*;

例如:使用一个枚举类的常量对象时,使用一个接口的内部接口时等

递归

  • 递归:指在当前方法内调用自己的这种现象。
  • 递归的分类:
    • 递归分为两种,直接递归和间接递归。
    • 直接递归称为方法自身调用自己。
    • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
  • 注意事项
    • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
    • 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。

计算1-100之间所有自然数的和

public class RecursionMethod1{
	public static void main(String[] args) {
		int sum = sum(100);
		System.out.println("1-100的和:" + sum);
	}

	public static int sum(int n){
		if(n == 1){
			return 1;
		}else{
			return n + sum(n-1);
		}
	}
}

1561731643079

求n!

1573725058457

public class RecursionMethod2{
	public static void main(String[] args) {
		int jieCheng = jieCheng(10);
		System.out.println("10的阶乘是:" + jieCheng);
	}
	public static int jieCheng(int n){
		if(n <= 1){
			return 1;
		}else{
			return n * jieCheng(n-1);
		}
	}
}

1561731297753

计算斐波那契数列(Fibonacci)的第n个值

规律:一个数等于前两个数之和,

​ f(0) =1,

​ f(1) = 1,

​ f(2) = f(0) + f(1) =2,

​ f(3) = f(1) + f(2) = 3,

​ f(4) = f(2) + f(3) = 5

​ …

​ f(n) = f(n-2) + f(n-1);

public class RecursionMethod3{
	public static void main(String[] args) {
		Count c = new Count();
		
		System.out.println("f(10):" + c.f(10));
		System.out.println("f方法被调用的总次数:" + c.total);
	}
}
class Count{
	int total = 0;
	public int f(int n){
		total++;
		if(n <= 1){
			return 1;
		}else{
			return f(n-2) + f(n-1);
		}
	}
}

1561733124831

练习

1、描述:猴子吃桃子问题,猴子第一天摘下若干个桃子,当即吃了所有桃子的一半,还不过瘾,又多吃了一个。第二天又将仅剩下的桃子吃掉了一半,又多吃了一个。以后每天都吃了前一天剩下的一半多一个。到第十天,只剩下一个桃子。试求第一天共摘了多少桃子?

1573725022751

2、有n级台阶,一次只能上1步或2步,共有多少种走法?

1573724181996

3、

1573724277746

对象数组

数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。

即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。

注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。

示例一:

(1)定义圆Circle类,包含radius半径属性,getArea()求面积方法,getPerimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法

(2)在测试类中创建长度为5的Circle[]数组,用来装5个圆对象,并给5个圆对象的半径赋值为[1,10)的随机值

class Test16_ObjectArray{
	public static void main(String[] args){
		//要在数组中存储5个圆对象
		//声明一个可以用来存储圆对象的数组
		Circle[] arr = new Circle[5];
		//for(int i=0; i<arr.length; i++){
		//	System.out.println(arr[i]);
		//}
		//System.out.println(arr[0].radius);//NullPointerException
		
		//给元素赋值
		//元素的类型是:Circle,应该给它一个Circle的对象
		//arr[0] = 1.2;//错误的
		//arr[0]相当于它是一个Circle类型的变量,也是对象名,必须赋值为对象
		/*
		arr[0] =  new Circle();
		arr[0].radius = 1.2;
		System.out.println(arr[0].radius);
		*/
		
		//创建5个对象,半径随机赋值为[1,10)的随机值
		//Math.random()==>[0,1)
		//Math.random()*9==>[0,9)
		//Math.random()*9+1==>[1,10)
		for(int i=0; i<arr.length; i++){
			arr[i] = new Circle();//有对象才有半径
			arr[i].radius = Math.random()*9+1;
		}
		
		
		//遍历显示圆对象的信息
		for(int i=0; i<arr.length; i++){
			//arr[i]是一个Circle的对象,就可以调用Circle类中的属性和方法
			System.out.println(arr[i].getInfo());
		}
	}
}
class Circle{
	double radius;
	public double getArea(){
		return 3.14 * radius * radius;
	}
	public double getPerimeter(){
		return 3.14 * 2 * radius;
	}
	public String getInfo(){
		return "半径:" + radius +",面积:" + getArea() + ",周长:" + getPerimeter();
	}
}

对象数组的内存图分析

6、对象数组内存分析

练习1

(1)定义学生类Student

​ 声明姓名和成绩实例变量,

​ getInfo()方法:用于返回学生对象的信息

(2)测试类ObjectArrayTest的main中创建一个可以装3个学生对象的数组,并且按照学生成绩排序,显示学生信息

public class ObjectArrayTest {
	public static void main(String[] args) {
		Student[] arr = new Student[3];
		arr[0] = new Student();
		arr[0].name = "张三";
		arr[0].score = 89;
		
		arr[1] = new Student();
		arr[1].name = "李四";
		arr[1].score = 84;
		
		arr[2] = new Student();
		arr[2].name = "王五";
		arr[2].score = 85;
		
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length-1; j++) {
				if(arr[j].score > arr[j+1].score){
					Student temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i].getInfo());
		}
	}
}
class Student{
	String name;
	int score;
	public String getInfo(){
		return "姓名:" + name + ",成绩:" + score;
	}
}
class Test18_ObjectArrayExer2_2{
	public static void main(String[] args){
		//创建一个可以装3个学生对象的数组
		Student[] arr = new Student[3];//只是申明这个数组,可以用来装3个学生,此时里面没有学生对象
		
		//从键盘输入
		java.util.Scanner input = new java.util.Scanner(System.in);
		for(int i=0;i<arr.length; i++){
			System.out.println("请输入第" + (i+1) + "个学生信息:");
			arr[i] = new Student();
			
			System.out.print("姓名:");
			arr[i].name = input.next();
			
			System.out.print("成绩:");
			arr[i].score = input.nextInt();
		}
		
		//先显示一下目前的顺序
		for(int i=0; i<arr.length; i++){
			System.out.println(arr[i].getInfo());
		}
		
		System.out.println("------------------------------------------");
		//冒泡排序
		for(int i=1; i<arr.length; i++){
			for(int j=0; j<arr.length-i; j++){
				//arr[j] > arr[j+1]//错误的
				if(arr[j].score > arr[j+1].score){
					//交换两个元素,这里是两个学生对象,所以temp也得是Student类型
					Student temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		//再显示一下目前的顺序
		for(int i=0; i<arr.length; i++){
			System.out.println(arr[i].getInfo());
		}
	}
}
class Student{
	String name;
	int score;//使用int或double都可以
	
	public String getInfo(){
		return "姓名:" + name +",成绩:" + score;
	}
}

面向对象基础(中)

封装

封装概述

为什么需要封装?

  • 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
  • 我们使用的电脑,内部有CPU、硬盘、键盘、鼠标等等,每一个部件通过某种连接方式一起工作,但是各个部件之间又是独立的
  • 现实生活中,每一个个体与个体之间是有边界的,每一个团体与团体之间是有边界的,而同一个个体、团体内部的信息是互通的,只是对外有所隐瞒。

面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。

随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
  • 低耦合:仅对外暴露少量的方法用于使用

隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

如何实现封装呢?

通俗的讲,封装就是把该隐藏的隐藏起来,该暴露的暴露出来。那么暴露的程度如何控制呢?就是依赖访问控制修饰符,也称为权限修饰符来控制。

访问控制修饰符来控制相应的可见边界,边界有如下:

(1)类

(2)包

(3)子类

(4)模块:Java9之后引入

权限修饰符

权限修饰符:public,protected,缺省,private

修饰符 本类 本包 其他包子类 其他包非子类
private × × ×
缺省 × ×
protected ×
public

外部类:public和缺省

成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private

提示:protected修饰非静态成员,跨包时,只能在子类的非静态成员中访问,在静态成员中无论是否创建对象都不能访问。

本包非子类与子类
package com.atguigu.test01.access1;

public class Father {
	public int a;
	protected int b;
	int c;
	private int d;
	
	public static int e;
	protected static int f;
	static int g;
	private static int h;
}

class Mother{
	public Mother(){
		
	}
}
package com.atguigu.test01.access1;

//本包非子类中
public class Other {
	public static void method(){
		Father obj = new Father();
		System.out.println(obj.a);
		System.out.println(obj.b);
		System.out.println(obj.c);
//		System.out.println(obj.d);//跨类不可见
		
		System.out.println(Father.e);
		System.out.println(Father.f);
		System.out.println(Father.g);
//		System.out.println(h);//跨类不可见
	}
	
	public void fun(){
		Father obj = new Father();
		System.out.println(obj.a);
		System.out.println(obj.b);
		System.out.println(obj.c);
//		System.out.println(obj.d);//跨类不可见
		
		System.out.println(Father.e);
		System.out.println(Father.f);
		System.out.println(Father.g);
//		System.out.println(h);//跨类不可见
	}
}
package com.atguigu.test01.access1;

//本包子类中
public class Sub extends Father{
	public static void method(){
		//静态直接访问非静态都不行
/*		System.out.println(a);
		System.out.println(b);
		System.out.println(c);
		System.out.println(d);*/
		
		Father obj = new Father();
		System.out.println(obj.a);
		System.out.println(obj.b);
		System.out.println(obj.c);
//		System.out.println(obj.d);//跨类不可见
		
		System.out.println(e);
		System.out.println(f);
		System.out.println(g);
//		System.out.println(h);//跨类不可见
	}
	
	public void fun(){
		System.out.println(a);
		System.out.println(b);
		System.out.println(c);
//		System.out.println(d);//跨类不可见
		
		System.out.println(e);
		System.out.println(f);
		System.out.println(g);
//		System.out.println(h);//跨类不可见
	}
}

image-20200304104635023

跨包子类和非子类
package com.atguigu.test01.access1;

public class Father {
	public int a;
	protected int b;
	int c;
	private int d;
	
	public static int e;
	protected static int f;
	static int g;
	private static int h;
}
package com.atguigu.test01.other;

import com.atguigu.test01.access1.Father;

public class Another {
	public static void method(){
		Father obj = new Father();
		System.out.println(obj.a);
//		System.out.println(obj.b);//跨包非子类不可见
//		System.out.println(obj.c);//跨包不可见
//		System.out.println(obj.d);//跨类不可见
		
		System.out.println(Father.e);
//		System.out.println(Father.f);//跨包非子类不可见
//		System.out.println(Father.g);//跨包不可见
//		System.out.println(h);//跨类不可见
	}
	
	public void fun(){
		Father obj = new Father();
		System.out.println(obj.a);
//		System.out.println(obj.b);//跨包非子类不可见
//		System.out.println(obj.c);//跨包不可见
//		System.out.println(obj.d);//跨类不可见
		
		System.out.println(Father.e);
//		System.out.println(Father.f);//跨包非子类不可见
//		System.out.println(Father.g);//跨包不可见
//		System.out.println(h);//跨类不可见
	}
}
package com.atguigu.test01.other;

import com.atguigu.test01.access1.Father;

public class Son extends Father{
	public static void method(){
		//静态直接访问非静态都不行
/*		System.out.println(a);
		System.out.println(b);
		System.out.println(c);
		System.out.println(d);*/
		
		Father obj = new Father();
		System.out.println(obj.a);
//		System.out.println(obj.b);//跨包的静态成员
						//不能访问非静态的protected
//		System.out.println(obj.c);//跨包不可见
//		System.out.println(obj.d);//跨类不可见
		
		System.out.println(e);
		System.out.println(f);
//		System.out.println(g);//跨包不可见
//		System.out.println(h);//跨类不可见
	}
	
	public void fun(){
		System.out.println(a);
		System.out.println(b);
//		System.out.println(c);//跨包不可见
//		System.out.println(d);//跨类不可见
		
		System.out.println(e);
		System.out.println(f);
//		System.out.println(g);//跨包不可见
//		System.out.println(h);//跨类不可见
	}
}

image-20200304105258795

缺省的类
package com.atguigu.test01.access1;

class Mother {
	
}
package com.atguigu.test01.access1;

public class Daughter extends Mother{

}
package com.atguigu.test01.other;

//Mother类是缺省的,跨包不能使用
public class Daughter extends Mother{

}

image-20200304105646328

示例四:公共的类缺省的构造器,跨包使用问题
package com.atguigu.test01.access1;

public class Fu {
	Fu(){
		
	}
}
package com.atguigu.test01.access1;

public class Zi extends Fu{

}
package com.atguigu.test01.other;

import com.atguigu.test01.access1.Fu;

public class Zi extends Fu{
	Zi() {
		super();
	}
}
package com.atguigu.test01.access1;

public class Neighbor {
	public static void main(String[] args) {
		Fu f = new Fu();
	}
}
package com.atguigu.test01.other;

import com.atguigu.test01.access1.Fu;

public class AnotherNeighbor {
	public static void main(String[] args) {
		Fu f = new Fu();
	}
}

image-20200304110742600

成员变量/属性私有化问题

成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)或者可以说只要能通过get/set操作的就是事物的属性,哪怕它没有对应的成员变量。

成员变量封装的目的

  • 隐藏类的实现细节
  • 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
  • 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

实现步骤

使用 private 修饰成员变量

private 数据类型 变量名 ;

代码如下:

public class Chinese {
    private static String country;
    private String name;
  	private int age;
    private boolean marry;
}

提供 getXxx方法 / setXxx 方法,可以访问成员变量,代码如下:

public class Chinese {
  	private static String country;
    private String name;
  	private int age;
    private boolean marry;
    
    public static void setCountry(String c){
        country = c;
    }
    
    public static String getCountry(){
        return country;
    }

	public void setName(String n) {
		name = n;
    }

    public String getName() {
        return name;
	}

    public void setAge(int a) {
        age = a;
    }

    public int getAge() {
        return age;
    }
    
    public void setMarry(boolean m){
        marry = m;
    }
    
    public boolean isMarry(){
        return marry;
    }
}

如何解决局部变量与成员变量同名问题

当局部变量与类变量(静态成员变量)同名时,在类变量前面加“类名.”;

当局部变量与实例变量(非静态成员变量)同名时,在实例变量前面加“this.”

public class Chinese {
  	private static String country;
    private String name;
  	private int age;
    
    public static void setCountry(String country){
        Chinese.country = country;
    }
    
    public static String getCountry(){
        return country;
    }

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

    public String getName() {
        return name;
	}

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

    public int getAge() {
        return age;
    }
}

练习

(1)定义矩形类Rectangle,

​ 声明静态变量sides,初始化为4,表示矩形边长的总数量;

​ 声明实例变量长和宽

​ 全部私有化,并提供相应的get/set方法

(2)在测试类中创建Rectangle对象,并调用相应的方法测试

(2)测试类ObjectArrayTest的main中创建一个可以装3个学生对象的数组,并且按照学生成绩排序,显示学生信息

构造器(Constructor)

我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。

可以,Java给我们提供了构造器。

构造器的作用

在创建对象的时候为实例变量赋初始值。

注意:构造器只为实例变量初始化,不为静态类变量初始化

构造器的语法格式

构造器又称为构造方法,那是因为它长的很像方法。但是和方法还有有所区别的。

【修饰符】 构造器名(){
    // 实例初始化代码
}
【修饰符】 构造器名(参数列表){
	// 实例初始化代码
}

代码如下:

public class Student {
	private String name;
	private int age;
	// 无参构造
  	public Student() {} 
 	// 有参构造
  	public Student(String name,int age) {
		this.name = name;
    	this.age = age; 
	}
  	
	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;
	}
}

注意事项:

  1. 构造器名必须与它所在的类名必须相同。
  2. 它没有返回值,所以不需要返回值类型,甚至不需要void
  3. 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
  4. 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
  5. 构造器是可以重载的,既可以定义参数,也可以不定义参数。
  6. 构造器的修饰符只能是权限修饰符,不能被其他任何修饰

练习

(1)声明一个员工类,

  • 包含属性:编号、姓名、薪资、性别,要求属性私有化,提供get/set,
  • 提供无参构造器和有参构造器
  • 提供getInfo()

(2)在测试类的main中分别用无参构造和有参构造创建员工类对象,调用getInfo

public class TestEmployee {
	public static void main(String[] args){
		//分别用无参构造和有参构造创建对象,调用getInfo
		Employee e1 = new Employee();
		System.out.println(e1.getInfo());
		
		Employee e2 = new Employee("1001","张三",110000,'男');
		System.out.println(e2.getInfo());
		
		e2.setSalary(120000);
		System.out.println(e2.getInfo());
		
		System.out.println("e1薪资:" + e1.getSalary());
	}
}
class Employee{
	private String id;
	private String name;
	private double salary;
	private char gender;
	
	//提供无参构造器和有参构造器
	public Employee(){
		
	}
	public Employee(String id, String name){
		this.id = id;
		this.name = name;
	}
	public Employee(String id, String name, double salary, char gender){
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.gender = gender;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	public char getGender() {
		return gender;
	}
	public void setGender(char gender) {
		this.gender = gender;
	}
	
	//提供getInfo()
	public String getInfo(){
		return "编号:" + id + ",姓名:" + name + ",薪资:" + salary + ",性别:" +gender;
	}
}

标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合JavaBean 的类,要求:

(1)类必须是具体的和公共的,

(2)并且具有无参数的构造方法,

(3)成员变量私有化,并提供用来操作成员变量的setget 方法。

public class ClassName{
  //成员变量
    
  //构造方法
  	//无参构造方法【必须】
  	//有参构造方法【建议】
  	
  //getXxx()
  //setXxx()
  //其他成员方法
}

编写符合JavaBean 规范的类,以学生类为例,标准代码如下:

public class Student {
	// 成员变量
	private String name;
	private int age;

	// 构造方法
	public Student() {
	}

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	// get/set成员方法
	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}
    
    //其他成员方法列表
    public String getInfo(){
        return "姓名:" + name + ",年龄:" + age;
    }
}

测试类,代码如下:

public class TestStudent {
	public static void main(String[] args) {
		// 无参构造使用
		Student s = new Student();
		s.setName("柳岩");
		s.setAge(18);
		System.out.println(s.getName() + "---" + s.getAge());
        System.out.println(s.getInfo());

		// 带参构造使用
		Student s2 = new Student("赵丽颖", 18);
		System.out.println(s2.getName() + "---" + s2.getAge());
        System.out.println(s2.getInfo());
	}
}

继承

继承的概述

生活中的继承

  • 财产:富二代

  • 样貌:如图所示:

  • 才华:如图所示:

继承的由来

如图所示:

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:

其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类超类(superclass)或者基类

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承的好处

  • 提高代码的复用性

  • 提高代码的扩展性

  • 类与类之间产生了关系,是学习多态的前提

继承的格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

【修饰符】 class 父类 {
	...
}

【修饰符】 class 子类 extends 父类 {
	...
}

继承演示,代码如下:

/*
 * 定义动物类Animal,做为父类
 */
class Animal {
    // 定义name属性
	String name; 
    // 定义age属性
    int age;
	// 定义动物的吃东西方法
	public void eat() {
		System.out.println(age + "岁的" + name + "在吃东西");
	}
}

/*
 * 定义猫类Cat 继承 动物类Animal
 */
class Cat extends Animal {
	// 定义一个猫抓老鼠的方法catchMouse
	public void catchMouse() {
		System.out.println("抓老鼠");
	}
}

/*
 * 定义测试类
 */
public class ExtendDemo01 {
	public static void main(String[] args) {
        // 创建一个猫类对象
		Cat cat = new Cat()// 为该猫类对象的name属性进行赋值
		cat.name = "Tom";
      
      	// 为该猫类对象的age属性进行赋值
		cat.age = 2;
        
        // 调用该猫的catchMouse()方法
		cat.catchMouse();
		
      	// 调用该猫继承来的eat()方法
      	cat.eat();
	}
}

演示结果:
抓老鼠
2岁的Tom在吃东西

继承的特点一:成员变量

父类成员变量私有化(private)

  • 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
  • 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。如图所示:

代码如下:

/*
 * 定义动物类Animal,做为父类
 */
class Animal {
    // 定义name属性
	private String name; 
    // 定义age属性
    public int age;
	// 定义动物的吃东西方法
	public void eat() {
		System.out.println(age + "岁的" + name + "在吃东西");
	}
}

/*
 * 定义猫类Cat 继承 动物类Animal
 */
class Cat extends Animal {
	// 定义一个猫抓老鼠的方法catchMouse
	public void catchMouse() {
		System.out.println("抓老鼠");
	}
}

/*
 * 定义测试类
 */
public class ExtendDemo01 {
	public static void main(String[] args) {
        // 创建一个猫类对象
		Cat cat = new Cat()// 为该猫类对象的name属性进行赋值
		//cat.name = "Tom";// 编译报错
      
      	// 为该猫类对象的age属性进行赋值
		cat.age = 2;
        
        // 调用该猫的catchMouse()方法
		cat.catchMouse();
		
      	// 调用该猫继承来的eat()方法
      	cat.eat();
	}
}

如图所示:

eclipse中Debug查看对象成员变量值的情况截图如下:

idea中Debug查看对象成员变量值的情况截图如下:

1576579518956

父子类成员变量重名

我们说父类的所有成员变量都会继承到子类中,那么如果子类出现与父类同名的成员变量会怎么样呢?

父类代码:

public class Father {
	public int i=1;
	private int j=1;
	public int k=1;
	public int getJ() {
		return j;
	}
	public void setJ(int j) {
		this.j = j;
	}
}

子类代码:

public class Son extends Father{
	public int i=2;
	private int j=2;
	public int m=2;
}	

现在想要在子类Son中声明一个test()方法,并打印这些所有变量的值,该如何实现?

public class Son extends Father{
	public int i=2;
	private int j=2;
	public int m=2;
	
	public void test() {
		System.out.println("父类继承的i:" + super.i);
		System.out.println("子类的i:" +i);
//		System.out.println(super.j);
		System.out.println("父类继承的j:" +getJ());
		System.out.println("子类的j:" +j);
		System.out.println("父类继承的k:" +k);
		System.out.println("子类的m:" +m);
	}	
}	

结论:

(1)当父类的成员变量私有化时,在子类中是无法直接访问的,所以是否重名不影响,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问;

(2)当父类的成员变量非私有时,在子类中可以直接访问,所以如果有重名时,就需要加“super.”进行区别。

使用格式:

super.父类成员变量名

以上test()调用结果:

public class TestSon{
	public static void main(String[] args){
		Son s = new Son();
		s.test();
	}
}
父类继承的i:1
子类的i:2
父类继承的j:1
子类的j:2
父类继承的k:1
子类的m:2

eclipse中Debug查看对象的成员变量的值截图如下:

1572422069292

idea中Debug查看对象的成员变量的值截图如下:

1576579731205

说明:虽然我们可以区分父子类的重名成员变量,但是实际开发中,我们不建议这么干。

继承的特点二:成员方法

我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

方法重写

比如新的手机增加来电显示头像的功能,代码如下:

class Phone {
	public void sendMessage(){
		System.out.println("发短信");
	}
	public void call(){
		System.out.println("打电话");
	}
	public void showNum(){
		System.out.println("来电显示号码");
	}
}

//智能手机类
class NewPhone extends Phone {
	
	//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
	public void showNum(){
		//调用父类已经存在的功能使用super
		super.showNum();
		//增加自己特有显示姓名和图片功能
		System.out.println("显示来电姓名");
		System.out.println("显示头像");
	}
}

public class ExtendsDemo06 {
	public static void main(String[] args) {
      	// 创建子类对象
      	NewPhone np = new NewPhone()// 调用父类继承而来的方法
        np.call();
      
      	// 调用子类重写的方法
      	np.showNum();

	}
}

小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。

注意事项:

1.@Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留

2.必须保证父子类之间方法的名称相同,参数列表也相同。
3.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。

注意:如果返回值类型是基本数据类型和void,那么必须是相同

4.子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > 缺省 > private

5.几种特殊的方法不能被重写

  • 静态方法不能被重写
  • 私有等在子类中不可见的方法不能被重写
  • final方法不能被重写

方法的重载

(1)同一个类中

class Test{
	public int max(int a, int b){
		return a > b ? a : b;
	}
	public double max(double a, double b){
		return a > b ? a : b;
	}
	public int max(int a, int b,int c){
		return max(max(a,b),c);
	}
}

(2)父子类中

class Father{
	public void print(int i){
		System.out.println("i = " + i);
	}
}
class Son extends Father{
	public void print(int i,int j){
		System.out.println("i = " + i  ",j = " + j);
	}
}

对于Son类,相当于有两个print方法,一个形参列表是(int i),一个形参列表(int i, int j)

继承的特点三:构造方法

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?

首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。

    所以子类是无法继承父类构造方法的。

  2. 构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量

    所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。代码如下:

class Fu {
  private int n;
  Fu(){
    System.out.println("Fu()");
  }
}
class Zi extends Fu {
  Zi(){
    // super(),调用父类构造方法
    super();
    System.out.println("Zi()");
  }  
}
public class ExtendsDemo07{
  public static void main (String args[]){
    Zi zi = new Zi();
  }
}
输出结果:
Fu()
Zi()

如果父类没有无参构造怎么办?

public class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	//其他成员方法省略
}
public class Student extends Person{
	private int score;
}

此时子类代码报错。

解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。

public class Student extends Person{
	private int score;

	public Student(String name, int age) {
		super(name, age);
	}
	public Student(String name, int age, int score) {
		super(name, age);
		this.score = score;
	}
	
	//其他成员方法省略
}

结论:

子类对象实例化过程中必须先完成从父类继承的成员变量的实例初始化,这个过程是通过调用父类的实例初始化方法来完成的。

  • super():表示调用父类的无参实例初始化方法,要求父类必须有无参构造,而且可以省略不写;
  • super(实参列表):表示调用父类的有参实例初始化方法,当父类没有无参构造时,子类的构造器首行必须写super(实参列表)来明确调用父类的哪个有参构造(其实是调用该构造器对应的实例初始方法)
  • super()和super(实参列表)都只能出现在子类构造器的首行

形式一:

class A{

}
class B extends A{

}

class Test{
    public static void main(String[] args){
        B b = new B();
        //A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
        //但是因为都是默认的,没有打印语句,看不出来
    }
}

形式二:

class A{
	A(){
		System.out.println("A类无参构造器");
	}
}
class B extends A{

}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
		//B类默认有一个无参构造,
		//B类的默认无参构造中会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"
    }
}

形式三:

class A{
	A(){
		System.out.println("A类无参构造器");
	}
}
class B extends A{
	B(){
		System.out.println("B类无参构造器");
	}
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
		//B类显示声明一个无参构造,        
		//B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

形式四:

class A{
	A(){
		System.out.println("A类无参构造器");
	}
}
class B extends A{
	B(){
        super();
		System.out.println("B类无参构造器");
	}
}
class Test{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个无参构造,
		//B类显示声明一个无参构造,        
		//B类的无参构造中明确写了super(),表示调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

形式五:

class A{
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
	B(){
		System.out.println("B类无参构造器");
	}
}
class Test05{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
		//B类显示声明一个无参构造,        
		//B类的无参构造没有写super(...),表示默认调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}

image-20200227141228450

image-20200227141051954

形式六:

class A{
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
	B(){
		super();
		System.out.println("B类无参构造器");
	}
}
class Test06{
    public static void main(String[] args){
        B b = new B();
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
		//B类显示声明一个无参构造,        
		//B类的无参构造明确写super(),表示调用A类的无参构造
        //编译报错,因为A类没有无参构造
    }
}

image-20200303183542807

形式七:

class A{
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
	B(int a){
		super(a);
		System.out.println("B类有参构造器");
	}
}
class Test07{
    public static void main(String[] args){
        B b = new B(10);
        //A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
		//B类显示声明一个有参构造,        
		//B类的有参构造明确写super(a),表示调用A类的有参构造
        //会打印“A类有参构造器"和"B类有参构造器"
    }
}

形式八:

class A{
    A(){
        System.out.println("A类无参构造器");
    }
	A(int a){
		System.out.println("A类有参构造器");
	}
}
class B extends A{
    B(){
        super();//可以省略,调用父类的无参构造
        System.out.println("B类无参构造器");
    }
	B(int a){
		super(a);//调用父类有参构造
		System.out.println("B类有参构造器");
	}
}
class Test8{
    public static void main(String[] args){
        B b1 = new B();
        B b2 = new B(10);
    }
}

继承的特点四:单继承限制

  1. Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} 	//ok
class C extends AB...	//error
  1. Java支持多层继承(继承体系)。
class A{}
class B extends A{}
class C extends B{}

顶层父类是Object类。所有的类默认继承Object,作为父类。

  1. 子类和父类是一种相对的概念。

    例如:B类对于A来说是子类,但是对于C类来说是父类

  2. 一个父类可以同时拥有多个子类

继承练习

练习1

(1)父类Graphic图形
包含属性:name(图形名),属性私有化,不提供无参构造,只提供有参构造
包含求面积getArea():返回0.0
求周长getPerimeter()方法:返回0.0
显示信息getInfo()方法:返回图形名称、面积、周长

(2)子类Circle圆继承Graphic图形
包含属性:radius
重写求面积getArea()和求周长getPerimeter()方法,显示信息getInfo()加半径信息

(3)子类矩形Rectange继承Graphic图形
包含属性:length、width
重写求面积getArea()和求周长getPerimeter()方法

public class Graphic {
	private String name;

	public Graphic(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

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

	public double getArea() {
		return 0.0;
	}

	public double getPerimeter() {
		return 0.0;
	}

	/*
	 * this对象:调用当前方法的对象,如果是Graphic对象,那么就会执行Graphic的getArea()和getPerimeter()
	 * this对象:调用当前方法的对象,如果是Circle对象,那么就会执行Circle的getArea()和getPerimeter()
	 * this对象:调用当前方法的对象,如果是Rectangle对象,那么就会执行Rectangle的getArea()和getPerimeter()
	 */
	public String getInfo() {
		return "图形:" + name + ",面积:" + getArea() + ",周长:" + getPerimeter();
	}
}
public class Circle extends Graphic {
	private double radius;

	public Circle(String name, double radius) {
		super(name);
		this.radius = radius;
	}

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}

	@Override//表示这个方法是重写的方法
	public double getArea() {
		return Math.PI * radius * radius;
	}

	@Override//表示这个方法是重写的方法
	public double getPerimeter() {
		return Math.PI * radius * 2;
	}

	/*@Override//表示这个方法是重写的方法
	public String getInfo() {
		return super.getInfo() + ",半径:" + radius;
	}*/
	
}
public class Rectangle extends Graphic {
	private double length;
	private double width;
	
	public Rectangle(String name, double length, double width) {
		super(name);
		this.length = length;
		this.width = width;
	}

	public double getLength() {
		return length;
	}

	public void setLength(double length) {
		this.length = length;
	}

	public double getWidth() {
		return width;
	}

	public void setWidth(double width) {
		this.width = width;
	}

	@Override
	public double getArea() {
		return length*width;
	}

	@Override
	public double getPerimeter() {
		return 2*(length + width);
	}
}
public class TestGraphicExer3 {
	public static void main(String[] args) {
		Graphic g = new Graphic("通用图形");
		System.out.println(g.getInfo());
		
		Circle c = new Circle("圆", 1.2);
		System.out.println(c.getInfo());//调用getInfo()方法的对象是c
		
		Rectangle r = new Rectangle("矩形", 3, 5);
		System.out.println(r.getInfo());
	}
}

练习2

(1)声明一个银行储蓄卡类,

​ 包含属性:账户,余额

​ 包含取款 public void withdraw(double money)

​ 存款 pubic void save(double money)

​ 获取账户信息: public String getInfo() 可以返回账户和余额

(2)声明一个银行信用卡类,继承储蓄卡类

​ 增加属性:可透支额度,最多可透支金额

​ 重写存款 public void withdraw(double money),可透支

​ 存款 pubic void save(double money),需要恢复可透支额度

(3)在测试类中,分别创建两种卡对象,测试

练习3

1、声明父类:Person类
包含属性:姓名,年龄,性别
属性私有化,get/set
包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男

2、声明子类:Student类,继承Person类
新增属性:score成绩
属性私有化,get/set
包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,成绩:89

3、声明子类:Teacher类,继承Person类
新增属性:salary薪资
属性私有化,get/set
包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,薪资:10000

public class Person {
	private String name;
	private int age;
	private char gender;
	public Person(String name, int age, char gender) {
		super();
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
	public Person() {
		super();
	}
	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 char getGender() {
		return gender;
	}
	public void setGender(char gender) {
		this.gender = gender;
	}
	
	//包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男
	public String getInfo(){
		return "姓名:" + name + ",年龄:" + age +",性别:" + gender;
	}
}
public class Student extends Person {
	private int score;

	public Student() {
	}

	public Student(String name, int age, char gender, int score) {
		setName(name);
		setAge(age);
		setGender(gender);
		this.score = score;
	}

	public int getScore() {
		return score;
	}

	public void setScore(int score) {
		this.score = score;
	}
	//包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,成绩:89
	public String getInfo(){
		//方式一:
//		return "姓名:" + getName() + ",年龄:" + getAge() + ",成绩:" + score;
		
		//方法二:
		return super.getInfo() + ",成绩:" + score;
	}
	
}
public class Teacher extends Person {
	private double salary;

	public Teacher() {
	}

	public Teacher(String name, int age, char gender, double salary) {
		setName(name);
		setAge(age);
		setGender(gender);
		this.salary = salary;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}
	
	//包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,薪资:10000
	public String getInfo(){
		return super.getInfo() + ",薪资:" + salary;
	}
}
public class TestPersonExer2 {
	public static void main(String[] args) {
		Person p = new Person("张三", 23, '男');
		System.out.println(p.getInfo());
		
		Student s = new Student("陈琦", 25, '男', 89);
		System.out.println(s.getInfo());
		
		Teacher t = new Teacher("柴林燕", 18, '女', 11111);
		System.out.println(t.getInfo());
	}
}

this和super关键字

this关键字

this的含义

this代表当前对象

this使用位置

  • this在实例初始化相关的代码块和构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁
  • this在非静态实例方法中:表示调用该方法的对象,即谁在调用,this就代表谁。
  • this不能出现在静态代码块和静态方法中

this使用格式

(1)this.成员变量名

  • 当方法的局部变量与当前对象的成员变量重名时,就可以在成员变量前面加this.,如果没有重名问题,就可以省略this.
  • this.成员变量会先从本类声明的成员变量列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员变量列表中查找

(2)this.成员方法

  • 调用当前对象的成员方法时,都可以加”this.”,也可以省略,实际开发中都省略
  • 当前对象的成员方法,先从本类声明的成员方法列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员方法列表中查找

(3)this()或this(实参列表)

  • 只能调用本类的其他构造器
  • 必须在构造器的首行
  • 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了”this(【实参列表】)”,否则会发生递归调用死循环

super关键字

super的含义

super代表当前对象中从父类的引用的

super使用的前提

  • 通过super引用父类的xx,都是在子类中仍然可见的
  • 不能在静态代码块和静态方法中使用super

super的使用格式

(1)super.成员变量

在子类中访问父类的成员变量,特别是当子类的成员变量与父类的成员变量重名时。

public class Person {
	private String name;
	private int age;
	//其他代码省略
}
public class Student extends Person{
	private int score;
	//其他成员方法省略
}
public class Test{
    public static void main(String[] args){
    	Student stu = new Student();
    }
}

1561984785190

public class Test{
    public static void main(String[] args){
    	Son s = new Son();
    	s.test(30);
    }
}
class Father{
	int a = 10;
}
class Son extends Father{
	int a = 20;
	public void test(int a){
		System.out.println(super.a);//10
		System.out.println(this.a);//20
		System.out.println(a);//30
	}
}

(2)super.成员方法

在子类中调用父类的成员方法,特别是当子类重写了父类的成员方法时

public class Test{
    public static void main(String[] args){
    	Son s = new Son();
    	s.test();
    }
}
class Father{
	public void method(){
		System.out.println("aa");
	}
}
class Son extends Father{
	public void method(){
		System.out.println("bb");
	}
	public void test(){
		method();//bb
		this.method();//bb
		super.method();//aa
	}
}

(3)super()或super(实参列表)

在子类的构造器首行,用于表示调用父类的哪个实例初始化方法

super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

就近原则和追根溯源原则

找变量

  • 没有super和this

    • 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量,
    • 如果不是局部变量,先从当前执行代码的本类去找成员变量
    • 如果从当前执行代码的本类中没有找到,会往上找父类的(非private,跨包还不能是缺省的)
  • this :代表当前对象

    • 通过this找成员变量时,先从当前执行代码的本类中找,没有的会往上找父类的(非private,跨包还不能是缺省的)。
  • super :代表父类的

    • 通过super找成员变量,直接从当前执行代码所在类的父类找
    • super()或super(实参列表)只能从直接父类找
    • 通过super只能访问父类在子类中可见的(非private,跨包还不能是缺省的)

注意:super和this都不能出现在静态方法和静态代码块中,因为super和this都是存在与对象中的

找方法

  • 没有super和this

    • 先从当前对象(调用方法的对象)的本类找,如果没有,再从直接父类找,再没有,继续往上追溯
  • this

    • 先从当前对象(调用方法的对象)的本类找,如果没有,再从父类继承的可见的方法列表中查找
  • super

    • 直接从当前对象(调用方法的对象)的父类继承的可见的方法列表中查找

找构造器

  • this()或this(实参列表):只从本类中,不会再往上追溯
  • super()或super(实参列表):只从直接父类找,不会再往上追溯

练习

情形1
class Father{
	int a = 10;
	int b = 11;
}
class Son extends Father{
	int a = 20;
	
	public void test(){
		//子类与父类的属性同名,子类对象中就有两个a
		System.out.println("父类的a:" + super.a);//10    直接从父类局部变量找
		System.out.println("子类的a:" + this.a);//20   先从本类成员变量找
		System.out.println("子类的a:" + a);//20  先找局部变量找,没有再从本类成员变量找
		
		//子类与父类的属性不同名,是同一个b
		System.out.println("b = " + b);//11  先找局部变量找,没有再从本类成员变量找,没有再从父类找
		System.out.println("b = " + this.b);//11   先从本类成员变量找,没有再从父类找
		System.out.println("b = " + super.b);//11  直接从父类局部变量找
	}
	
	public void method(int a){
		//子类与父类的属性同名,子类对象中就有两个成员变量a,此时方法中还有一个局部变量a
		System.out.println("父类的a:" + super.a);//10  直接从父类局部变量找
		System.out.println("子类的a:" + this.a);//20  先从本类成员变量找
		System.out.println("局部变量的a:" + a);//30  先找局部变量
	}
    
    public void fun(int b){
        System.out.println("b = " + b);//13  先找局部变量
		System.out.println("b = " + this.b);//11  先从本类成员变量找
		System.out.println("b = " + super.b);//11  直接从父类局部变量找
    }
}
public class TestInherite2 {
	public static void main(String[] args) {
		Son son = new Son();
		System.out.println(son.a);//20
		System.out.println(son.b);//11
		
		son.test();
		
		son.method(30);
        
        son.fun(13);
	}
}
情形2
public class Test{
    public static void main(String[] args){
    	Son s = new Son();
    	System.out.println(s.getNum());//10   没重写,先找本类,没有,找父类
    	
    	Daughter d = new Daughter();
    	System.out.println(d.getNum());//20  重写了,先找本类
    }
}
class Father{
	protected int num = 10;
	public int getNum(){
		return num;
	}
}
class Son extends Father{
	private int num = 20;
}
class Daughter extends Father{
	private int num = 20;
	public int getNum(){
		return num;
	}
}
情形3
public class Test{
    public static void main(String[] args){
    	Son s = new Son();
    	s.test();
    	
    	Daughter d = new Daughter();
    	d.test();
    }
}
class Father{
	protected int num = 10;
	public int getNum(){
		return num;
	}
}
class Son extends Father{
	private int num = 20;
	public void test(){
		System.out.println(getNum());//10  本类没有找父类
		System.out.println(this.getNum());//10  本类没有找父类
		System.out.println(super.getNum());//10  本类没有找父类
	}
}
class Daughter extends Father{
	private int num = 20;
	public int getNum(){
		return num;
	}
	public void test(){
		System.out.println(getNum());//20  先找本类
		System.out.println(this.getNum());//20  先找本类
		System.out.println(super.getNum());//10  直接找父类
	}
}

成员变量初始化

成员变量初始化方式

成员变量有默认值

类别 具体类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
数据类型 默认值
引用类型 数组,类,接口 null

我们知道类中成员变量都有默认值,但是现在我们要为成员变量赋默认值以外的值,我们该怎么办呢?

显式赋值

public class Student{
    public static final String COUNTRY = "中华人民共和国";
	private static String school = "尚硅谷";
	private String name;
	private char gender = '男';
}

显式赋值,一般都是赋常量值

代码块

如果成员变量想要初始化的值不是一个硬编码的常量值,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值,该怎么办呢?

  • 静态初始化块:为静态变量初始化
【修饰符】 class 类名{
    static{
		静态初始化
	}
}
  • 实例初始化:为实例变量初始化
【修饰符】 class 类名{
    {
		实例初始化块
	}
}

静态初始化块:在类初始化时由类加载器调用执行,每一个类的静态初始化只会执行一次,早于实例对象的创建。

实例初始化块:每次new实例对象时自动执行,每new一个对象,执行一次。

public class Student{
	private static String school;
	private String name;
	private char gender;
	
	static{
		//获取系统属性,这里只是说明school的初始化过程可能比较复杂
		school = System.getProperty("school");
		if(school==null) {
			school = "尚硅谷";
		}
	}
	{
		String info = System.getProperty("gender");
		if(info==null) {
			gender = '男';
		}else {
			gender = info.charAt(0);
		}
	}
	public static String getSchool() {
		return school;
	}
	public static void setSchool(String school) {
		Student.school = school;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public char getGender() {
		return gender;
	}
	public void setGender(char gender) {
		this.gender = gender;
	}

}

构造器

我们发现,显式赋值和实例初始化块为每一个实例对象的实例变量初始化的都是相同的值,那么我们如果想要不同的实例对象初始化为不同的值,怎么办呢?此时我们可以考虑使用构造器,在new对象时由对象的创建者决定为当前对象的实例变量赋什么值。

注意:构造器只为实例变量初始化,不为静态类变量初始化

为实例变量初始化,再new对象时由对象的创建者决定为当前对象的实例变量赋什么值。

类初始化

1、类初始化的目的:为类中的静态变量进行赋值。

2、实际上,类初始化的过程时在调用一个()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化()方法体中。
clinit are the static initialization blocks for the class, and static field initialization

(1)静态类成员变量的显式赋值语句

(2)静态代码块中的语句

3、整个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。

示例代码1:单个类

public class Test{
    public static void main(String[] args){
    	Father.test();
    }
}
class Father{
	private static int a = getNumber();//这里调用方法为a变量显式赋值的目的是为了看到这个过程
	static{
		System.out.println("Father(1)");
	}
	private static int b = getNumber();
	static{
		System.out.println("Father(2)");
	}
	
	public static int getNumber(){
		System.out.println("getNumber()");
		return 1;
	}
	
	public static void test(){
		System.out.println("Father:test()");
	}
}
运行结果:
getNumber()
Father(1)
getNumber()
Father(2)
Father:test()

示例代码2:父子类

public class Test{
    public static void main(String[] args){
    	Son.test();
        System.out.println("-----------------------------");
        Son.test();
    }
}
class Father{
	private static int a = getNumber();
	static{
		System.out.println("Father(1)");
	}
	private static int b = getNumber();
	static{
		System.out.println("Father(2)");
	}
	
	public static int getNumber(){
		System.out.println("Father:getNumber()");
		return 1;
	}
}
class Son extends Father{
	private static int a = getNumber();
	static{
		System.out.println("Son(1)");
	}
	private static int b = getNumber();
	static{
		System.out.println("Son(2)");
	}
	
	public static int getNumber(){
		System.out.println("Son:getNumber()");
		return 1;
	}
	
	public static void test(){
		System.out.println("Son:test()");
	}	
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son:test()
-----------------------------
Son:test()

结论:

每一个类都有一个类初始化方法()方法,然后子类初始化时,如果发现父类加载和没有初始化,会先加载和初始化父类,然后再加载和初始化子类。一个类,只会初始化一次。

实例初始化

1、实例初始化的目的:为类中非静态成员变量赋值

2、实际上我们编写的代码在编译时,会自动处理代码,整理出一个()的类初始化方法,还会整理出一个或多个的(…)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。

init is the (or one of the) constructor(s) for the instance, and non-static field initialization.

实例初始化方法的方法体,由四部分构成:

(1)super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,没写,默认就是super()

(2)非静态实例变量的显示赋值语句

(3)非静态代码块

(4)对应构造器中的代码

特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面

3、执行特点:

  • 创建对象时,才会执行
  • 每new一个对象,都会完成该对象的实例初始化
  • 调用哪个构造器,就是执行它对应的实例初始化方法
  • 创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)

示例代码1:单个类

public class Test{
    public static void main(String[] args){
    	Father f1 = new Father();
    	Father f2 = new Father("atguigu");
    }
}
class Father{
	private int a = getNumber();
	private String info;
	{
		System.out.println("Father(1)");
	}
	Father(){
		System.out.println("Father()无参构造");
	}
	Father(String info){
		this.info = info;
		System.out.println("Father(info)有参构造");
	}
	private int b = getNumber();
	{
		System.out.println("Father(2)");
	}
	
	public int getNumber(){
		System.out.println("Father:getNumber()");
		return 1;
	}
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father()无参构造
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father(info)有参构造

1562072678317

示例代码2:父子类

public class Test{
    public static void main(String[] args){
    	Son s1 = new Son();
        System.out.println("-----------------------------");
    	Son s2 = new Son("atguigu");
    }
}
class Father{
	private int a = getNumber();
	private String info;
	{
		System.out.println("Father(1)");
	}
	Father(){
		System.out.println("Father()无参构造");
	}
	Father(String info){
		this.info = info;
		System.out.println("Father(info)有参构造");
	}
	private int b = getNumber();
	{
		System.out.println("Father(2)");
	}
	
	public static int getNumber(){
		System.out.println("Father:getNumber()");
		return 1;
	}
}
class Son extends Father{
	private int a = getNumber();
	{
		System.out.println("Son(1)");
	}
	private int b = getNumber();
	{
		System.out.println("Son(2)");
	}
	public Son(){
		System.out.println("Son():无参构造");
	}
	public Son(String info){
		super(info);
		System.out.println("Son(info):有参构造");
	}
	public static int getNumber(){
		System.out.println("Son:getNumber()");
		return 1;
	}
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father()无参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son():无参构造
-----------------------------
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father(info)有参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son(info):有参构造

示例代码3:父子类,方法有重写

public class Test{
    public static void main(String[] args){
    	Son s1 = new Son();
    	System.out.println("-----------------------------");
    	Son s2 = new Son("atguigu");
    }
}
class Father{
	private int a = getNumber();
	private String info;
	{
		System.out.println("Father(1)");
	}
	Father(){
		System.out.println("Father()无参构造");
	}
	Father(String info){
		this.info = info;
		System.out.println("Father(info)有参构造");
	}
	private int b = getNumber();
	{
		System.out.println("Father(2)");
	}
	
	public int getNumber(){
		System.out.println("Father:getNumber()");
		return 1;
	}
}
class Son extends Father{
	private int a = getNumber();
	{
		System.out.println("Son(1)");
	}
	private int b = getNumber();
	{
		System.out.println("Son(2)");
	}
	public Son(){
		System.out.println("Son():无参构造");
	}
	public Son(String info){
		super(info);
		System.out.println("Son(info):有参构造");
	}
	public int getNumber(){
		System.out.println("Son:getNumber()");
		return 1;
	}
}
运行结果:
Son:getNumber()  //子类重写getNumber()方法,那么创建子类的对象,就是调用子类的getNumber()方法,因为当前对象this是子类的对象。
Father(1)
Son:getNumber()
Father(2)
Father()无参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son():无参构造
-----------------------------
Son:getNumber()
Father(1)
Son:getNumber()
Father(2)
Father(info)有参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son(info):有参构造

6.6.4 类初始化与实例初始化

类初始化肯定优先于实例初始化。

类初始化只做一次。

实例初始化是每次创建对象都要进行。

public class Test{
    public static void main(String[] args){
    	Son s1 = new Son();
    	System.out.println("----------------------------");
    	Son s2 = new Son();
    }
}
class Father{
	static{
		System.out.println("Father:static");
	}
	{
		System.out.println("Father:not_static");
	}
	Father(){
		System.out.println("Father()无参构造");
	}
}
class Son extends Father{
	static{
		System.out.println("Son:static");
	}
	{
		System.out.println("Son:not_static");
	}
	Son(){
		System.out.println("Son()无参构造");
	}
}
运行结果:
Father:static
Son:static
Father:not_static
Father()无参构造
Son:not_static
Son()无参构造
----------------------------
Father:not_static
Father()无参构造
Son:not_static
Son()无参构造

多态

引入

多态是继封装、继承之后,面向对象的第三大特性。

生活中,比如求面积的功能,圆、矩形、三角形实现起来是不一样的。跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。那么此时就会出现各种子类的类型。

但是Java是强类型静态语言,既每一个变量在使用之前必须声明它确切的类型,然后之后的赋值和运算时都是严格按照这个数据类型来处理的。例如:

int num = 10;
String str = "hello";
Student stu = new Student();

但是,有的时候,我们在设计一个数组、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。

例如:想要设计一个数组用来存储各种图形的对象,并且按照各种图形的面积进行排序,但是具体存储的对象可能有圆、矩形、三角形等,那么各种图形的求面积方式又是不同的。

例如:想要设计一个方法,它的功能是比较两个图形的面积大小,返回面积较大的那个图形对象。那么此时形参和返回值类型是图形类型,但是不知道它具体是哪一种图形类型。

Circle[] arr = new Circle[长度]; //只能装圆形对象
Rectangle[] arr = new Rectangle[长度]; //只能装矩形对象
//无法统一管理各种图形对象,例如:给各种图形对象按照面积排序


//需要重载很多个方法,增加一种具体的图形,就需要增加一个方法
public static Circle maxArea(Circle c1, Circle c2){//只能比较两个圆对象
    
}
public static Rectangle maxArea(Rectangle r1, Rectangle r2){//只能比较两个矩形对象
    
}

这个时候,Java就引入了多态。

定义

格式

父类类型 变量名 = 子类对象;

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

例如:

class Person{
	private String name;
	private int age;
	
    Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    
	public void speak(){
		System.out.println(name + "说:我今年" + age);
	}
}
class Man extends Person{
    Man(String name, int age){
        super(name,age);
    }
}
class Woman extends Person{
    Woman(String name, int age){
        super(name,age);
    }
}
class Test{
	public static void main(String[] args){
		Person[] arr = new Person[2];
		arr[0] = new Man("张三",23);
		arr[1] = new Woman("如花",18);
		
		for(int i=0; i<arr.length; i++){
			arr[i].speak();
		}
		System.out.println("------------------------");
		
		show(new Man("张三",23));
		show(new Woman("如花",18));
	}
	
	public static void show(Person p){
		p.speak();
	}
}

编译时类型与运行时类型不一致问题

  • 编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法;

  • 运行时,看“子类”,一定是执行子类重写的方法体;

代码如下:

定义父类:

public class Animal {  
    public void eat(){
        System.out.println("吃~~~");
    }
}  

定义子类:

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse(){
        System.out.println("抓老鼠"); 
    }
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        // 多态形式,创建对象
        Animal a1 = new Cat();  
        // 调用的是 Cat 的 eat
        a1.eat();    
        //a1.catchMouse();//错误,catchMouse()是子类扩展的方法,父类中没有
        /*
        多态引用,编译时,看“父类”,只能调用父类声明的方法;
        	    运行时,看“子类”,一定是执行子类重写的方法体;
        */

        // 多态形式,创建对象
        Animal a2 = new Dog(); 
        // 调用的是 Dog 的 eat
        a2.eat();               
    }  
}

多态的应用

多态应用在形参实参

父类类型作为方法形式参数,子类对象为实参。

代码如下:

public class Test01 {
	public static void main(String[] args) {
		showAnimalEat(new Dog()); //形参 Animal a,实参new Dog() 
								//实参给形参赋值   Animal a = new Dog()   多态引用
		showAnimalEat(new Cat());//形参 Animal a,实参new Cat() 
								//实参给形参赋值   Animal a = new Cat()   多态引用
	}
	
	/*
	 * 设计一个方法,可以查看所有动物的吃的行为
	 * 关注的是所有动物的共同特征:eat()
	 * 所以形参,设计为父类的类型
	 * 	此时不关注子类特有的方法
	 */
	public static void showAnimalEat (Animal a){
        a.eat();
//        a.catchMouse();//错误,因为a现在编译时类型是Animal,只能看到父类中有的方法
    }

}

多态应用在数组

数组元素类型声明为父类类型,实际存储的是子类对象

public class Test02 {
	public static void main(String[] args) {
		/*
		 * 声明一个数组,可以装各种动物的对象,看它们吃东西的样子
		 */
		Animal[] arr = new Animal[2]; //此时不是new Animal的对象,而是new Animal[]的数组对象
									//在堆中开辟了长度为5的数组空间,用来装Animal或它子类对象的地址
		arr[0] = new Cat();//多态引用   左边arr[0] 是Animal类型,右边是new Cat()
							//把Cat对象,赋值给Animal类型的变量
		arr[1] = new Dog();
		
		for (int i = 0; i < arr.length; i++) {
			arr[i].eat();
//			arr[i].catchMouse();错误,因为arr[i]现在编译时类型是Animal,只能看到父类中有的方法
		}
	}
}

多态应用在返回值

方法的返回值类型声明为父类的类型,实际返回值是子类对象

public class Test03 {
	public static void main(String[] args) {
		Animal c = buy("猫咪");
		System.out.println(c.getClass());
		c.eat();
	}
	/*
	 * 设计一个方法,可以购买各种动物的对象,此时不确定是那种具体的动物
	 * 
	 * 返回值类型是父类的对象
	 * 
	 * 多态体现在   返回值类型  Animal ,实际返回的对象是子类的new Cat(),或new Dog()
	 */
	public static Animal buy(String name){
        if("猫咪".equals(name)){
            return new Cat();
        }else if("小狗".equals(name)){
            return new Dog();
        }
        return null;
    }
}

多态练习

练习1:

(1)声明父类Traffic,包含方法public void drive()
(2)声明子类Car,Bicycle等,并重写drive方法
(3)在测试类的main中创建一个数组,有各种交通工具,遍历调用drive()方法
模拟马路上跑的各种交通工具

public class Traffic {
	public void drive(){
        System.out.println("~~~~");
    }
}
public class Car extends Traffic {
	@Override
	public void drive() {
		System.out.println("滴滴滴...");
	}
}
public class Bicycle extends Traffic {
	@Override
	public void drive() {
		System.out.println("蹬蹬蹬。。。");
	}
}
public class TestExer1 {
	public static void main(String[] args) {
		//右边这些是用匿名对象,初始化数组
		Traffic[] arr = {new Car(),new Bicycle(),new Car(),new Bicycle()};
		for (int i = 0; i < arr.length; i++) {
			arr[i].drive();
		}
	}
}

练习2:

(1)声明一个父类Person类,public void toilet()

(2)声明一个子类Woman类,重写方法
(3)声明一个子类Man类,重写方法
(4)在测试类中声明一个方法,
public static void goToToilet(Person p){
p.toilet();
}
在main中,创建不同子类对象,调用goToToilet方法进行测试

public class Person {
	public void toilet(){
        System.out.println("~~~");
    }
}
public class Man extends Person {
	@Override
	public void toilet() {
		System.out.println("站着..");
	}
}
public class Woman extends Person {
	@Override
	public void toilet() {
		System.out.println("坐着..");
	}
}
public class TestPerson {
	public static void main(String[] args) {
		goToToilet(new Woman());//隐含了Person p = new Woman();
		goToToilet(new Man());//隐含了Person p = new Man();
	}
	
	public static void goToToilet(Person p){
		p.toilet();
	}
}

练习3:

1、声明一个父类Employee员工类型,有属性,姓名(String)
有方法,public double earning() 用于返回实发工资,默认返回0
public String getInfo():显示姓名和实发工资

2、声明一个子类SalaryEmployee正式工,继承父类Employee,增加属性,薪资,工作日天数,请假天数
重写方法,public double earning()返回实发工资,实发工资 = 薪资 - 薪资/工作日天数 * 请假天数,

3、声明一个子类HourEmployee小时工,继承父类Employee
有属性,工作小时数,每小时多少钱
重写方法,public double earning()返回实发工资, 实发工资 = 每小时多少钱 * 小时数

4、声明一个子类Manager经理,继承SalaryEmployee,增加属性:奖金比例
重写方法,public double earning()返回实发工资,实发工资 = (薪资 - 薪资/工作日天数 请假天数)(1+奖金比例)

5、你现在是财务,需要查看每个人的实发工资,并查看工资总额。
声明一个员工数组,存储各种员工,并遍历显示他们的姓名和实发工资,并计算所有员工的工资总额

public class Employee {
	private String name;

	public Employee(String name) {
		super();
		this.name = name;
	}

	public Employee() {
		super();
	}

	public String getName() {
		return name;
	}

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

	public double earning(){
        return 0.0;
    }

	public String getInfo() {
		return "姓名:" + name + ",实发工资:" + earning();
	}
}
public class SalaryEmployee extends Employee {
	private double salary;
	private int workingDays;//工作日天数,
	private double offDays;//请假天数

	public SalaryEmployee() {
		super();
	}

	public SalaryEmployee(String name,  double salary, int workingDays, double offDays) {
		super(name);
		this.salary = salary;
		this.workingDays = workingDays;
		this.offDays = offDays;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public int getWorkingDays() {
		return workingDays;
	}

	public void setWorkingDays(int workingDays) {
		this.workingDays = workingDays;
	}

	public double getOffDays() {
		return offDays;
	}

	public void setOffDays(double offDays) {
		this.offDays = offDays;
	}

	/*
	 * 重写方法,public double earning()返回实发工资, 
		实发工资 = 薪资 - 薪资/工作日天数 * 请假天数
	 */
	@Override
	public double earning() {
		return salary - salary/workingDays * offDays;
	}

}
public class HourEmployee extends Employee {
	private double moneyPerHour;
	private double hours;
	
	public HourEmployee() {
		super();
	}

	public HourEmployee(String name, double moneyPerHour, double hours) {
		super(name);
		this.moneyPerHour = moneyPerHour;
		this.hours = hours;
	}

	public double getMoneyPerHour() {
		return moneyPerHour;
	}

	public void setMoneyPerHour(double moneyPerHour) {
		this.moneyPerHour = moneyPerHour;
	}

	public double getHours() {
		return hours;
	}

	public void setHours(double hours) {
		this.hours = hours;
	}

	/*
	 * 重写方法,public double earning()返回实发工资, 
		实发工资 = 每小时多少钱 * 小时数	
	 */
	@Override
	public double earning() {
		return moneyPerHour * hours;
	}

}
public class Manager extends SalaryEmployee {
	private double commisionPer;

	public Manager() {
		super();
	}

	public Manager(String name,  double salary, int workingDays, double offDays, double commisionPer) {
		super(name, salary, workingDays, offDays);
		this.commisionPer = commisionPer;
	}

	public double getCommisionPer() {
		return commisionPer;
	}

	public void setCommisionPer(double commisionPer) {
		this.commisionPer = commisionPer;
	}

	@Override
	public double earning() {
		return super.earning() * (1+commisionPer);
	}
}
public class TestEmployee {
	public static void main(String[] args) {
		Employee[] all = new Employee[3];
		
		all[0] = new HourEmployee("张三", 50, 50);
		all[1] = new SalaryEmployee("李四", 10000, 22, 1);
		all[2] = new Manager("老王", 20000, 22, 0, 0.3);
		
		double sum = 0;
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i].getInfo());
			sum += all[i].earning();
		}
		System.out.println("总额:" + sum);
	}
}

向上转型与向下转型

首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。这个和基本数据类型的转换是不同的。

但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

class Animal {  
    void eat(){
        System.out.println("~~~"); 
    } 
}  

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

class Test{
    public static void main(String[] args){
        Cat a = new Cat();//a编译时类型是Cat
        Animal b = a;//b编译时类型是Animal
        Object c = a;//c编译时类型是Object
        
        //运行时类型
        System.out.println(a.getClass());
        System.out.println(b.getClass());
        System.out.println(c.getClass());
        //以上输出都一样,都是Cat类型
        
       	//a,b,c的编译时类型不同
    	//通过a能调用Cat中所有方法,包括从父类继承的,包括自己扩展的
    	//通过b只能调用Animal类及它的父类有的方法,不能调用Cat扩展的方法
    	//通过c只能调用Object类才有的方法
    }
}

为什么要类型转换呢?

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。

但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子类特有的方法,必须做类型转换。

  • 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型

    • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
    • 但是,运行时,仍然是对象本身的类型
    • 此时,一定是安全的,而且也是自动完成的
  • 向下转型:当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型

    • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
    • 但是,运行时,仍然是对象本身的类型
    • 此时,不一定是安全的,需要使用(类型)进行强制类型转换
    • 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断

示例代码:

public class Test {    public static void main(String[] args) {        // 向上转型          Animal a = new Cat();          a.eat(); 				// 调用的是 Cat 的 eat        // 向下转型          Cat c = (Cat)a;               c.catchMouse(); 		// 调用的是 Cat 的 catchMouse                // 向下转型          //Dog d = (Dog)a;     //这段代码可以通过编译,但是运行时,却报出了ClassCastException         //这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,		//不符合类型转换的定义。        //d.watchHouse();        // 调用的是 Dog 的 watchHouse                 Animal a2 = new Animal();       // Dog d2 = (Dog)a2;//这段代码可以通过编译,但是运行时,却报出了ClassCastException        // d2.watchHouse(); // 调用的是 Dog 的 watchHouse    }  }

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

变量名/对象 instanceof 数据类型 

所以,转换前,我们最好先做一个判断,代码如下:

public class Test {    public static void main(String[] args) {        // 向上转型          Animal a = new Cat();          a.eat();               // 调用的是 Cat 的 eat        // 向下转型          if (a instanceof Cat){            Cat c = (Cat)a;                   c.catchMouse();        // 调用的是 Cat 的 catchMouse        } else if (a instanceof Dog){            Dog d = (Dog)a;                   d.watchHouse();       // 调用的是 Dog 的 watchHouse        }    }  }

那么,哪些instanceof判断会返回true呢?

  • 对象/变量的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
  • 对象/变量的运行时类型<= instanceof后面数据类型,才为true

示例代码:

class Person{	//方法代码省略...}class Woman extends Person{    //方法代码省略...}class ChineseWoman extends Woman{	//方法代码省略...}class Man extends Person{    }
/*
 * 1、instanceof 前面的对象与后面的类型有没有要求
 * 	instanceof 前面的对象的编译时类型,必须与 instanceof后面的类型有直系关系
 
 * 2、instanceof 什么时候返回true
 * 	instanceof 前面的对象的运行时类型,确实 <= instanceof后面的类型,直系关系
 * 
 */
public class TestInstanceof {
	public static void main(String[] args) {
		Man m = new Man();
//		System.out.println(m instanceof Woman);//错误  m的编译时类型是Man,它和Woman不是直系关系
		
		Person p1 = new Man();
		System.out.println(p1 instanceof Woman);
        //可以,p1的编译时类型是Person,它和Woman是直系关系
		//但是p1的运行时类型是Man,返回false
        
		Person p2 = new Woman();
		System.out.println(p2 instanceof Woman);
        //p2的编译时类型是Person,它和Woman是直系关系
		//p2的运行时类型是Woman,返回true
        
		Person p3 = new ChineseWoman();
		System.out.println(p2 instanceof Woman);
        //p3的编译时类型是Person,它和Woman是直系关系
        //但是p3的运行时类型是ChineseWoman, ChineseWoman<=Woman,所以返回true
	}
}
 public class Test{
     public static void main(String[] args){
        Person p1 = new Person();
        Person p2 = new Woman();
        Person p3 = new ChineseWoman();
        Person p4 = new Man();
        Object p5 = new Woman();
        ChineseWoman p6 = new ChineseWoman();
        
        //因为p1的运行时类型是Person类型,编译时类型是Person
        System.out.println(p1 instanceof Object);//true  Person < Object类型
        System.out.println(p1 instanceof Person);//true  Person = Person类型
        System.out.println(p1 instanceof Woman);//false	 Person > Woman类型
        System.out.println(p1 instanceof ChineseWoman);//false Person > ChineseWoman类型
        System.out.println(p1 instanceof Man);//false  Person > Man类型
        System.out.println("------------------------");
        
      //因为p2的运行时类型是Woman类型,编译时类型是Person
        System.out.println(p2 instanceof Object);//true Woman < Object类型
        System.out.println(p2 instanceof Person);//true Woman < Person类型
        System.out.println(p2 instanceof Woman);//true Woman = Woman类型
        System.out.println(p2 instanceof ChineseWoman);//false Woman > ChineseWoman类型
        System.out.println(p2 instanceof Man);//false Woman 和Man 是平级关系,没有父子类关系
        System.out.println("------------------------");
        
        //因为p3的运行时类型是ChineseWoman,编译时类型是Person
        System.out.println(p3 instanceof Object);//true ChineseWoman < Object类型
        System.out.println(p3 instanceof Person);//true ChineseWoman < Person类型
        System.out.println(p3 instanceof Woman);//true ChineseWoman < Woman类型
        System.out.println(p3 instanceof ChineseWoman);//true ChineseWoman = ChineseWoman类型
        System.out.println(p3 instanceof Man);//false ChineseWoman 和 Man 是叔侄挂心,不是父子类关系
        System.out.println("------------------------");
        
        //因为p4的运行时类型是Man,编译时类型是Person
        System.out.println(p4 instanceof Object);//true Man < Object类型
        System.out.println(p4 instanceof Person);//true Man < Person类型
        System.out.println(p4 instanceof Woman);//false Woman 和Man 是平级关系,没有父子类关系
        System.out.println(p4 instanceof ChineseWoman);//false ChineseWoman 和 Man 是叔侄挂心,不是父子类关系
        System.out.println(p4 instanceof Man);//true Man = Man类型
        System.out.println("------------------------");
        
        //因为p5的运行时类型是Woman类型,编译时类型是Object
        System.out.println(p5 instanceof Object);//true Woman < Object类型
        System.out.println(p5 instanceof Person);//true Woman < Person类型
        System.out.println(p5 instanceof Woman);//true Woman = Woman类型
        System.out.println(p5 instanceof ChineseWoman);//false Woman > ChineseWoman类型
        System.out.println(p5 instanceof Man);//false Woman 和Man 是平级关系,没有父子类关系
        System.out.println("------------------------");
 
        //因为p6的运行时类型是ChineseWoman,编译时类型是ChineseWoman
        System.out.println(p6 instanceof Object);//true ChineseWoman < Object类型
        System.out.println(p6 instanceof Person);//true ChineseWoman < Person类型
        System.out.println(p6 instanceof Woman);//true ChineseWoman < Woman类型
        System.out.println(p6 instanceof ChineseWoman);//true ChineseWoman = ChineseWoman类型
//        System.out.println(p6 instanceof Man);//编译不通过,因为p6的编译时类型是ChineseWoman,和Man不是直系亲属关系
        System.out.println("------------------------");
    }
 }

练习题

1、声明一个父类Employee员工类型,
有属性,姓名(String),出生日期(MyDate类型,也是自定义的含年,月,日属性日期类型)
有方法,public abstract double earning()
public String getInfo():显示姓名和实发工资

2、声明一个子类SalaryEmployee正式工,继承父类Employee
增加属性,薪资,工作日天数,请假天数
重写方法,public double earning()返回实发工资, 实发工资 = 薪资 - 薪资/工作日天数 * 请假天数,
重写方法,public String getInfo():显示姓名和实发工资,月薪,工作日天数,请假天数

3、声明一个子类HourEmployee小时工,继承父类Employee
有属性,工作小时数,每小时多少钱
重写方法,public double earning()返回实发工资, 实发工资 = 每小时多少钱 * 小时数
重写方法,public String getInfo():显示姓名和实发工资,时薪,工作小时数
增加方法,public void leave():打印查看使用工具是否损坏,需要赔偿

4、声明一个子类Manager经理,继承SalaryEmployee
增加属性:奖金,奖金比例
重写方法,public double earning()返回实发工资, 实发工资 = (薪资 - 薪资/工作日天数 请假天数)(1+奖金比例)
重写方法,public String getInfo():显示姓名和实发工资,月薪,工作日天数,请假天数,奖金比例

5、声明一个员工数组,存储各种员工,
你现在是人事,从键盘输入当前的月份,需要查看每个人的详细信息。
如果他是正式工(包括SalaryEmployee和Manager),并且是本月生日的,祝福生日快乐,通知领取生日礼物。如果是HourEmployee显示小时工,就进行完工检查,即调用leave方法

public abstract class Employee {	private String name;	private MyDate birthday;	public Employee(String name, MyDate birthday) {		super();		this.name = name;		this.birthday = birthday;	}	public Employee(String name, int year, int month, int day) {		super();		this.name = name;		this.birthday = new MyDate(year, month, day);	}	public Employee() {		super();	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}	public MyDate getBirthday() {		return birthday;	}	public void setBirthday(MyDate birthday) {		this.birthday = birthday;	}		public abstract double earning();		public String getInfo(){		return "姓名:" + name + ",生日:" + birthday.getInfo() +",实发工资:" + earning();	}}
public class SalaryEmployee extends Employee {	private double salary;	private int workingDays;//工作日天数,	private double offDays;//请假天数	public SalaryEmployee() {		super();	}	public SalaryEmployee(String name, int year, int month, int day, double salary, int workingDays, double offDays) {		super(name, year, month, day);		this.salary = salary;		this.workingDays = workingDays;		this.offDays = offDays;	}	public SalaryEmployee(String name, MyDate birthday, double salary, int workingDays, double offDays) {		super(name, birthday);		this.salary = salary;		this.workingDays = workingDays;		this.offDays = offDays;	}	public double getSalary() {		return salary;	}	public void setSalary(double salary) {		this.salary = salary;	}	public int getWorkingDays() {		return workingDays;	}	public void setWorkingDays(int workingDays) {		this.workingDays = workingDays;	}	public double getOffDays() {		return offDays;	}	public void setOffDays(double offDays) {		this.offDays = offDays;	}	/*	 * 重写方法,public double earning()返回实发工资, 		实发工资 = 薪资 - 薪资/工作日天数 * 请假天数	 */	@Override	public double earning() {		return salary - salary/workingDays * offDays;	}		@Override	public String getInfo() {		return super.getInfo() + ",月薪:" + salary + ",工作日:" + workingDays +",请假天数:" + offDays;	}}
public class HourEmployee extends Employee {
	private double moneyPerHour;
	private double hours;
	
	public HourEmployee() {
		super();
	}

	public HourEmployee(String name, int year, int month, int day, double moneyPerHour, double hours) {
		super(name, year, month, day);
		this.moneyPerHour = moneyPerHour;
		this.hours = hours;
	}

	public HourEmployee(String name, MyDate birthday, double moneyPerHour, double hours) {
		super(name, birthday);
		this.moneyPerHour = moneyPerHour;
		this.hours = hours;
	}

	public double getMoneyPerHour() {
		return moneyPerHour;
	}

	public void setMoneyPerHour(double moneyPerHour) {
		this.moneyPerHour = moneyPerHour;
	}

	public double getHours() {
		return hours;
	}

	public void setHours(double hours) {
		this.hours = hours;
	}

	/*
	 * 重写方法,public double earning()返回实发工资, 
		实发工资 = 每小时多少钱 * 小时数	
	 */
	@Override
	public double earning() {
		return moneyPerHour * hours;
	}

	@Override
	public String getInfo() {
		return super.getInfo() + ",时薪:" + moneyPerHour + ",小时数:" + hours;
	}

	public void leave(){
		System.out.println("小时工,查看使用工具是否损坏,需要赔偿,然后拿钱走人");
	}
}
public class Manager extends SalaryEmployee {	private double commisionPer;	public Manager() {		super();	}	public Manager(String name, int year, int month, int day, double salary, int workingDays, double offDays,			double commisionPer) {		super(name, year, month, day, salary, workingDays, offDays);		this.commisionPer = commisionPer;	}	public Manager(String name, MyDate birthday, double salary, int workingDays, double offDays, double commisionPer) {		super(name, birthday, salary, workingDays, offDays);		this.commisionPer = commisionPer;	}	public double getCommisionPer() {		return commisionPer;	}	public void setCommisionPer(double commisionPer) {		this.commisionPer = commisionPer;	}	@Override	public double earning() {		return super.earning() * (1+commisionPer);	}	@Override	public String getInfo() {		return super.getInfo() + ",奖金比例:" + commisionPer;	}}
public class TestEmployee {
	public static void main(String[] args) {
		Employee[] all = new Employee[3];
		/*all[0] = new HourEmployee("张三", new MyDate(1990, 5, 1), 50, 50);
		all[1] = new SalaryEmployee("李四", new MyDate(1991, 1, 1), 10000, 22, 1);
		all[2] = new Manager("老王", new MyDate(1987, 12, 8), 20000, 22, 0, 0.3);*/
		
		all[0] = new HourEmployee("张三", 1990, 5, 1, 50, 50);
		all[1] = new SalaryEmployee("李四", 1991, 1, 1, 10000, 22, 1);
		all[2] = new Manager("老王", 1987, 12, 8, 20000, 22, 0, 0.3);
		
		//从键盘输入当前的月份
		Scanner input = new Scanner(System.in);
		System.out.print("请输入当前月份:");
		int month;
		while(true){
			month = input.nextInt();
			if(month>=1 && month<=12){
				break;
			}
		}
		input.close();
		
		for (int i = 0; i < all.length; i++) {
			System.out.println(all[i].getInfo());
			if(all[i] instanceof SalaryEmployee){
				if(month == all[i].getBirthday().getMonth()){
					System.out.println(all[i].getName() +"生日快乐,领取生日补助购物卡");
				}
			}else{
				HourEmployee he = (HourEmployee) all[i];
				he.leave();
			}
		}
	}
}

多态引用时关于成员变量与成员方法引用的原则

成员变量:只看编译时类型

如果直接访问成员变量,那么只看编译时类型

package com.atguigu.test05;

/*
 * 成员变量没有重写,只看编译时类型
 */
public class TestExtends {
	public static void main(String[] args) {
		Son s = new Son();
		System.out.println(s.a);//2,因为son的编译时类型是Son
		System.out.println(((Father)s).a);//1    ((Father)son)编译时类型,就是Father
		
		Father s2 = new Son();
		System.out.println(s2.a);//1 son2的编译时类型是Father
		System.out.println(((Son)s2).a);//2  ((Son)son2)编译时类型,就是Son
	}
}
class Father{
	int a = 1;
}
class Son extends Father{
	int a = 2;
}

非虚方法:只看编译时类型

在Java中的非虚方法有三种:

1、由invokestatic指令调用的static方法,这种方法在编译时确定在运行时不会改变。

javap -v .\Test.class

2、由invokespecial指令调用的方法,这些方法包括私有方法,实例构造方法和父类方法,这些方法也是在编译时已经确定,在运行时不会再改变的方法

3、由final关键字修饰的方法。虽然final方法是由invokevirtual指令进行调用的,但是final修饰的方法不能够进行在子类中进行覆盖,所以final修饰的方法是不能够在运行期进行动态改变的。在java语言规范中明确规定final方法就是非虚方法。

package com.atguigu.test09;

public class Test {

	public static void main(String[] args) {
		Father f = new Son();
		f.test();//只看编译时类型
        f.method();
	}
}
class Father{
	public static void test(){
		System.out.println("Father.test");
	}
    public void method(){
        System.out.println("Father.method");
        fun();//看运行时类型
        other();//看编译时类型
    }
    public void fun(){
        System.out.println("Father.fun");
    }
    private void other(){
        System.out.println("Father.other");
    }
}
class Son extends Father{
	public static void test(){
		System.out.println("son");
	}
    public void fun(){
        System.out.println("Son.fun");
    }
    private void other(){
        System.out.println("Son.other");
    }
}

小贴士:

静态方法不能被重写

调用静态方法最好使用“类名.”

虚方法:静态分派与动态绑定

在Java中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。

当我们通过“对象.方法”的形式,调用一个虚方法,我们要如何确定它具体执行哪个方法呢?

(1)静态分派:先看这个对象的编译时类型,在这个对象的编译时类型中找到最匹配的方法

最匹配的是指,实参的编译时类型与形参的类型最匹配

(2)动态绑定:再看这个对象的运行时类型,如果这个对象的运行时类重写了刚刚找到的那个最匹配的方法,那么执行重写的,否则仍然执行刚才编译时类型中的那个方法

没有重载有重写
abstract class Animal {  
    public abstract void eat();  
}  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

public class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
    }
}

如上代码在编译期间先进行静态分派:此时a的编译时类型是Animal类,所以去Animal类中搜索eat()方法,如果Animal类或它的父类中没有这个方法,将会报错。

而在运行期间动态的在进行动态绑定:a的运行时类型是Cat类,而子类重写了eat()方法,所以执行的是Cat类的eat方法。如果没有重写,那么还是执行Animal类在的eat()方法

有重载没有重写
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
	public void method(Daughter f) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		Father f = new Father();
		Father s = new Son();
		Father d = new Daughter();
        
        MyClass my = new MyClass();
		my.method(f);//father
		my.method(s);//father
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:因为my是MyClass类型,那么在MyClass类型中寻找最匹配的method方法。

而在运行期间动态的在进行动态绑定:即确定执行的是MyClass类中的method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,不是应该分别执行method(Father f)、method(Son s)、method(Daughter d)吗?

因为此时实参f,s,d编译时类型都是Father类型,因此method(Father f)是最合适的。

有重载没有重写
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MyClass();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//father
		my.method(s);//son
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:因为my是MyClass类型,那么在MyClass类型中寻找最匹配的method方法。

而在运行期间动态的在进行动态绑定:即确定执行的是MyClass类中的method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,这次为什么分别执行method(Father f)、method(Son s)?

因为此时实参f,s,d编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

有重载没有重写
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class MySub extends MyClass{
	public void method(Daughter d) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MySub();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//father
		my.method(s);//son
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:因为my是MyClass类型,那么在MyClass类型中寻找最匹配的method方法。

而在运行期间动态的在进行动态绑定:即确定执行的是MyClass类中的method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,my对象不是MySub类型吗,而MySub类型中有method(Daughter d)方法,那么my.method(d)语句应该执行MySub类型中的method(Daughter d)方法?

  • my变量在编译时类型是MyClass类型,那么在MyClass类中,只有method(Father f),method(Son s)方法,

  • f,s,d变量编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

  • 而在MySub类中并没有重写method(Father f)方法,所以仍然执行MyClass类中的method(Father f)方法
示有重载有重写
class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class MySub extends MyClass{
	public void method(Father d) {
		System.out.println("sub--");
	}
    public void method(Daughter d) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverloadOverride {
	public static void main(String[] args) {
		MyClass my = new MySub();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//sub--
		my.method(s);//son
		my.method(d);//sub--
	}
}

如上代码在编译期间先进行静态分派:因为my是MyClass类型,那么在MyClass类型中寻找最匹配的method方法。

而在运行期间动态的在进行动态绑定:即确定执行的是MyClass类中的method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,my对象不是MySub类型吗,而MySub类型中有method(Daughter d)方法,那么my.method(d)语句应该执行MySub类型中的method(Daughter d)方法?

  • my变量在编译时类型是MyClass类型,那么在MyClass类中,只有method(Father f),method(Son s)方法,

  • f,s,d变量编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

  • 而在MySub类中重写method(Father f)方法,所以执行MySub类中的method(Father f)方法

native关键字

native:本地的,原生的
用法:

​ 只能修饰方法

​ 表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。

​ 但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。

JVM内存的管理:

区域名称 作用
程序计数器 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址
本地方法栈 当程序中调用了native的本地方法时,本地方法执行期间的内存区域
方法区 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
堆内存 存储对象(包括数组对象),new来创建的,都存储在堆内存。
虚拟机栈 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。

修饰符一起使用问题?

外部类 成员变量 代码块 构造器 方法 局部变量
public × ×
protected × × ×
private × × ×
static × × ×
final × ×
abstract × × × ×
native × × × × ×

不能和abstract一起使用的修饰符?

(1)abstract和final不能一起修饰方法和类

(2)abstract和static不能一起修饰方法

(3)abstract和native不能一起修饰方法

(4)abstract和private不能一起修饰方法

static和final一起使用:

(1)修饰方法:可以,因为都不能被重写

(2)修饰成员变量:可以,表示静态常量

(3)修饰局部变量:不可以,static不能修饰局部变量

(4)修饰代码块:不可以,final不能修改代码块

(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类(后面讲)

final关键字

final:最终的,不可更改的,它的用法有:

修饰类

表示这个类不能被继承,没有子类

final class Eunuch{//太监类
	
}
class Son extends Eunuch{//错误
	
}

修饰方法

表示这个方法不能被子类重写

class Father{
	public final void method(){
		System.out.println("father");
	}
}
class Son extends Father{
	public void method(){//错误
		System.out.println("son");
	}
}

声明常量

final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。

如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)

public class Test{
    public static void main(String[] args){
    	final int MIN_SCORE = 0;
    	final int MAX_SCORE = 100;
    }
}
class Chinese{
	public static final String COUNTRY = "中华人民共和国";	
	private String name;
	public Chinese( String name) {
		super();
		this.name = name;
	}
	public Chinese() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	//final修饰的没有set方法
	public static String getCountry() {
		return COUNTRY;
	}
}

Object根父类

如何理解根父类

java.lang.Object是类层次结构的根类,即所有类的父类。每个类都使用 Object 作为超类。

  • Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用
  • 所有对象(包括数组)都实现这个类的方法。
  • 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
  	// ...
}

Object类的API

API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典 ,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码

​ 根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:

toString()

public String toString()

①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式”

②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S—>Generate toString()

③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()

因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。

例如自定义的Person类:

public class Person {  
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    // 省略构造器与Getter Setter
}

getClass()

public final Class<?> getClass():获取对象的运行时类型

因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法

public static void main(String[] args) {
	Object obj = new String();
	System.out.println(obj.getClass());//运行时类型
}

finalize()

protected void finalize():用于最终清理内存的方法

public class TestFinalize {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			MyData my = new MyData();
		}
		
		System.gc();//通知垃圾回收器来回收垃圾
		
		try {
			Thread.sleep(2000);//等待2秒再结束main,为了看效果
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
class MyData{

	@Override
	protected void finalize() throws Throwable {
		System.out.println("轻轻的我走了...");
	}
	
}

面试题:对finalize()的理解?

  • 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
  • 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码

  • 每一个对象的finalize方法只会被调用一次。

  • 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存

hashCode()

public int hashCode():返回每个对象的hash值。

hashCode 的常规协定:

  • ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
  • ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。

主要用于后面当对象存储到哈希表等容器中时,为了提高存储和查询性能用的。

public static void main(String[] args) {
	System.out.println("Aa".hashCode());//2112
	System.out.println("BB".hashCode());//2112
}

equals()

public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”

①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值

②我们可以选择重写,重写有些要求:

A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:

​ a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;

​ b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;

​ c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false

B:如果重写equals,那么一定要遵循如下几个原则:

​ a:自反性:x.equals(x)返回true

​ b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true

​ c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致

​ d:对称性:x.equals(y)与y.equals(x)结果应该一样

​ e:非空对象与null的equals一定是false

class User{
	private String host;
	private String username;
	private String password;
	public User(String host, String username, String password) {
		super();
		this.host = host;
		this.username = username;
		this.password = password;
	}
	public User() {
		super();
	}
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [host=" + host + ", username=" + username + ", password=" + password + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((host == null) ? 0 : host.hashCode());
		result = prime * result + ((password == null) ? 0 : password.hashCode());
		result = prime * result + ((username == null) ? 0 : username.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (host == null) {
			if (other.host != null)
				return false;
		} else if (!host.equals(other.host))
			return false;
		if (password == null) {
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}
	
}

面向对象基础(下)

枚举

概述

某些类型的对象是有限的几个,这样的例子举不胜举:

  • 星期:Monday(星期一)……Sunday(星期天)
  • 性别:Man(男)、Woman(女)
  • 月份:January(1月)……December(12月)
  • 季节:Spring(春天)……Winter(冬天)
  • 支付方式:Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银行卡)、CreditCard(信用卡)
  • 员工工作状态:Busy(忙)、Free(闲)、Vocation(休假)
  • 订单状态:Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配货)、Delivered(已发货)、Checked(已确认收货)、Return(退货)、Exchange(换货)、Cancel(取消)

枚举类型本质上也是一种类,只不过是这个类的对象是固定的几个,而不能随意让用户创建。

在JDK1.5之前,需要程序员自己通过特殊的方式来定义枚举类型。

在JDK1.5之后,Java支持enum关键字来快速的定义枚举类型。

JDK1.5之前

在JDK1.5之前如何声明枚举类呢?

  • 构造器加private私有化
  • 本类内部创建一组常量对象,并添加public static修饰符,对外暴露这些常量对象

示例代码:

public class TestEnum {
	public static void main(String[] args) {
		Season spring = Season.SPRING;
		System.out.println(spring);
	}
}
class Season{
	public static final Season SPRING = new Season();
	public static final Season SUMMER = new Season();
	public static final Season AUTUMN = new Season();
	public static final Season WINTER = new Season();
	
	private Season(){
		
	}
	
	public String toString(){
		if(this == SPRING){
			return "春";
		}else if(this == SUMMER){
			return "夏";
		}else if(this == AUTUMN){
			return "秋";
		}else{
			return "冬";
		}
	}
}

JDK1.5之后

语法格式:

【修饰符】 enum 枚举类名{
    常量对象列表
}

【修饰符】 enum 枚举类名{
    常量对象列表;
    
    其他成员列表;
}

示例代码:

public class TestEnum {
	public static void main(String[] args) {
		Season spring = Season.SPRING;
		System.out.println(spring);
	}
}
enum Season{
	SPRING,SUMMER,AUTUMN,WINTER
}

示例代码:

public class TestEnum {
	public static void main(String[] args) {
		Season spring = Season.SPRING;
		System.out.println(spring);
	}
}
enum Season{
	SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTER("冬");
	private final String description;
	
	private Season(String description){
		this.description = description;
	}
	
	public String toString(){//需要手动编写,无法使用Generate toString()...
		return description;
	}
}

枚举类的要求和特点:

  • 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
  • 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
  • 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数,
  • 如果枚举类需要的是有参构造,需要手动定义private的有参构造,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
  • 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
  • JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
  • 枚举类型如有其它属性,建议(不是必须)这些属性也声明为final的,因为常量对象在逻辑意义上应该不可变。

枚举类型常用方法

1.toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
2.name():返回的是常量名(对象名) 【很少使用】
3.ordinal():返回常量的次序号,默认从0开始
4.values():返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型,是一个静态方法
5.valueOf(String name):根据枚举常量对象名称获取枚举对象

示例代码:

public class TestEnum {
	public static void main(String[] args) {
		Season[] values = Season.values();
		for (int i = 0; i < values.length; i++) {
			switch(values[i]){
			case SPRING:
				System.out.println(values[i]+":春暖花开,万物复苏");
				break;
			case SUMMER:
				System.out.println(values[i]+":百花争艳,郁郁葱葱");
				break;
			case AUTUMN:
				System.out.println(values[i]+":菊桂飘香,百树凋零");
				break;
			case WINTER:
				System.out.println(values[i]+":梅花独开,大地一色");
				break;
			}
		}
	}
}
enum Season{
	SPRING,SUMMER,AUTUMN,WINTER
}

练习

案例:
1、声明月份枚举类Month:

(1)创建:1-12月常量对象

JANUARY,FEBRUARY,MARCH,APRIL,MAY,JUNE,JULY,AUGUST,SEPTEMBER,OCTOBER,NOVEMBER,DECEMBER

(2)声明两个属性:value(月份值,例如:JANUARY的value为1),
description(描述,例如:JANUARY的description为1月份是一年的开始)。

(3)声明一个有参构造,创建12个对象

(4)声明一个方法:public static Month getByValue(int value)

(5)手动重写toString():返回对象信息,例如:1->1月份是一年的开始。

2、在测试类中,从键盘输入1个1-12的月份值,获取对应的月份对象,并打印对象

包装类

包装类

Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),那么基本数据类型的数据就需要用包装类来包装。

序号 基本数据类型 包装类(java.lang包)
1 byte Byte
2 short Short
3 int Integer
4 long Long
5 float Float
6 double Double
7 char Character
8 boolean Boolean
9 void Void

装箱与拆箱

装箱:把基本数据类型转为包装类对象。

转为包装类的对象,是为了使用专门为对象设计的API和特性

拆箱:把包装类对象拆为基本数据类型。

转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等

基本数值——>包装对象

Integer i1 = new Integer(4);//使用构造函数函数
Integer i2 = Integer.valueOf(4);//使用包装类中的valueOf方法

包装对象——>基本数值

Integer i1 = new Integer(4);
int num1 = i1.intValue();

JDK1.5之后,可以自动装箱与拆箱。

注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。
Integer i = 1;
Double d = 1;//错误的,1是int类型

总结:对象(引用数据类型)能用的运算符有哪些?

(1)instanceof

(2)=:赋值运算符

(3)==和!=:用于比较地址,但是要求左右两边对象的类型一致或者是有父子类继承关系。

(4)对于字符串这一种特殊的对象,支持“+”,表示拼接。

包装类的一些API

基本数据类型和字符串之间的转换

(1)把基本数据类型转为字符串

int a = 10;
//String str = a;//错误的
//方式一:
String str = a + "";
//方式二:
String str = String.valueOf(a);

(2)把字符串转为基本数据类型

String转换成对应的基本类型 ,除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:

  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。

或把字符串转为包装类,然后可以自动拆箱为基本数据类型

  • public static Integer valueOf(String s):将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型
  • public static Long valueOf(String s):将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型
  • public static Double valueOf(String s):将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

int a = Integer.parseInt("整数的字符串");
double d = Double.parseDouble("小数的字符串");
boolean b = Boolean.parseBoolean("true或false");

int a = Integer.valueOf("整数的字符串");
double d = Double.valueOf("小数的字符串");
boolean b = Boolean.valueOf("true或false");

数据类型的最大最小值

Integer.MAX_VALUE和Integer.MIN_VALUE
Long.MAX_VALUE和Long.MIN_VALUE
Double.MAX_VALUE和Double.MIN_VALUE

字符转大小写

Character.toUpperCase('x');
Character.toLowerCase('X');

整数转进制

Integer.toBinaryString(int i) 
Integer.toHexString(int i)
Integer.toOctalString(int i)

包装类对象的缓存问题

包装类 缓存对象
Byte -128~127
Short -128~127
Integer -128~127
Long -128~127
Float 没有
Double 没有
Character 0~127
Boolean true和false
Integer i = 1;
Integer j = 1;
System.out.println(i == j);//true

Integer i = 128;
Integer j = 128;
System.out.println(i == j);//false

Integer i = new Integer(1);//新new的在堆中
Integer j = 1;//这个用的是缓冲的常量对象,在方法区
System.out.println(i == j);//false

Integer i = new Integer(1);//新new的在堆中
Integer j = new Integer(1);//另一个新new的在堆中
System.out.println(i == j);//false
@Test
public void test3(){
	Double d1 = 1.0;
	Double d2 = 1.0;
	System.out.println(d1==d2);//false 比较地址,没有缓存对象,每一个都是新new的
}

面试题

类型转换问题

@Test
public void test4(){
	Double d1 = 1.0;
	double d2 = 1.0;
	System.out.println(d1==d2);//true 和基本数据类型比较会自动拆箱,比较数据值
}

@Test
public void test2(){
	Integer i = 1000;
	double j = 1000;
	System.out.println(i==j);//true  会先将i自动拆箱为int,然后根据基本数据类型“自动类型转换”规则,转为double比较
}

@Test
public void test(){
	Integer i = 1000;
	int j = 1000;
	System.out.println(i==j);//true 会自动拆箱,按照基本数据类型进行比较
}

不可变对象

public class TestExam {
	public static void main(String[] args) {
		int i = 1;
		Integer j = new Integer(2);
		Circle c = new Circle();
		change(i,j,c);
		System.out.println("i = " + i);//1
		System.out.println("j = " + j);//2
		System.out.println("c.radius = " + c.radius);//10.0
	}
	
	/*
	 * 方法的参数传递机制:
	 * (1)基本数据类型:形参的修改完全不影响实参
	 * (2)引用数据类型:通过形参修改对象的属性值,会影响实参的属性值
	 * 这类Integer等包装类对象是“不可变”对象,即一旦修改,就是新对象,和实参就无关了
	 */
	public static void change(int a ,Integer b,Circle c ){
		a += 10;
//		b += 10;//等价于  b = new Integer(b+10);
		c.radius += 10;
		/*c = new Circle();
		c.radius+=10;*/
	}
}
class Circle{
	double radius;
}

抽象类

由来

抽象:即不具体、或无法具体

例如:当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现,而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体,我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类

语法格式

  • 抽象方法 : 没有方法体的方法。
  • 抽象类:被abstract所修饰的类。

抽象类的语法格式

【权限修饰符】 abstract class 类名{
    
}
【权限修饰符】 abstract class 类名 extends 父类{
    
}

抽象方法的语法格式

【其他修饰符】 abstract 返回值类型  方法名(【形参列表】);

注意:抽象方法没有方法体

代码举例:

public abstract class Animal {
    public abstract void run()}
public class Cat extends Animal {
    public void run (){
      	System.out.println("小猫在墙头走~~~")}
}
public class CatTest {
 	 public static void main(String[] args) {
        // 创建子类对象
        Cat c = new Cat(); 
       
        // 调用run方法
        c.run();
  	}
}
输出结果:
小猫在墙头走~~~

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

注意事项

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。

    理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

练习

练习1

定义一个几何图形父类Graphic。所有几何图形都应该具备一个计算面积的方法。但是不同的几何图形计算面积的方式完全不同。

abstract class Graphic{
	public abstract double getArea();
}
class Circle extends Graphic{
	private double radius;

	public Circle(double radius) {
		super();
		this.radius = radius;
	}

	public Circle() {
		super();
	}

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}

	@Override
	public double getArea() {
		return Math.PI * radius * radius;
	}
	
}
class Rectangle extends Graphic{
	private double length;
	private double width;
	public Rectangle(double length, double width) {
		super();
		this.length = length;
		this.width = width;
	}
	public Rectangle() {
		super();
	}
	public double getLength() {
		return length;
	}
	public void setLength(double length) {
		this.length = length;
	}
	public double getWidth() {
		return width;
	}
	public void setWidth(double width) {
		this.width = width;
	}
	@Override
	public double getArea() {
		return length * width;
	}
}

练习2

1、声明抽象父类:Person,包含抽象方法:
public abstract void walk();
public abstract void eat();

2、声明子类Man,继承Person
重写walk():大步流星走路
重写eat():狼吞虎咽吃饭
新增方法:public void smoke()实现为吞云吐雾

3、声明子类Woman,继承Person
重写walk():婀娜多姿走路
重写eat():细嚼慢咽吃饭
新增方法:public void buy()实现为买买买…

4、在测试类中创建子类对象,调用方法测试

public abstract class Person {
	public abstract void walk();
	public abstract void eat();
}
public class Man extends Person {

	@Override
	public void walk() {
		System.out.println("大步流星走路");
	}

	@Override
	public void eat() {
		System.out.println("狼吞虎咽吃饭");
	}

	public void smoke(){
		System.out.println("吞云吐雾");
	}
}
public class Woman extends Person {

	@Override
	public void walk() {
		System.out.println("婀娜多姿走路");
	}

	@Override
	public void eat() {
		System.out.println("细嚼慢咽吃饭");
	}
	
	public void buy(){
		System.out.println("买买买...");
	}
}
public class TestExer1 {

	public static void main(String[] args) {
		Man m = new Man();
		m.eat();
		m.walk();
		m.smoke();
		
		System.out.println("-------------------------");
		
		Woman w = new Woman();
		w.eat();
		w.walk();
		w.buy();
	}

}

接口

概述

生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?

 USB( Universal Serial Bus )是通用串行总线的英文缩写,是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。只须将设备插入计算机的USB端口中,系统会自动识别和配置。 有了USB,我们电脑需要提供的各种插槽的口越来越少,而能支持的其他设备的连接却越来越多。

​ 那么我们平时看到的电脑上的USB插口、以及其他设备上的USB插口是什么呢?

​ 其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范的一种具体设备而已。

​ 根据时代发展,USB接口标准经历了一代USB、第二代USB 2.0和第三代USB 3.0 。

​ USB规格第一次是于1995年,由Intel、IBM、Compaq、Microsoft、NEC、Digital、North Telecom等七家公司组成的USBIF(USB Implement Forum)共同提出,USBIF于1996年1月正式提出USB1.0规格,频宽为1.5Mbps。

USB2.0技术规范是有由Compaq、Hewlett Packard、Intel、Lucent、Microsoft、NEC、Philips共同制定、发布的,规范把外设数据传输速度提高到了480Mbps,被称为USB 2.0的高速(High-speed)版本.

USB 3.0是最新的USB规范,该规范由英特尔等公司发起,USB3.0的最大传输带宽高达5.0Gbps(640MB/s),USB3.0 引入全双工数据传输。5根线路中2根用来发送数据,另2根用来接收数据,还有1根是地线。也就是说,USB 3.0可以同步全速地进行读写操作。

USB版本 最大传输速率 速率称号 最大输出电流 推出时间
USB1.0 1.5Mbps(192KB/s) 低速(Low-Speed) 5V/500mA 1996年1月
USB1.1 12Mbps(1.5MB/s) 全速(Full-Speed) 5V/500mA 1998年9月
USB2.0 480Mbps(60MB/s) 高速(High-Speed) 5V/500mA 2000年4月
USB3.0 5Gbps(500MB/s) 超高速(Super-Speed) 5V/900mA 2008年11月
USB 3.1 10Gbps(1280MB/s) 超高速+(Super-speed+) 20V/5A 2013年12月

下面是USB2.0和USB3.0标准下的各类接口示意图:

​ 电脑边上提供了USB插槽,这个插槽遵循了USB的规范,只要其他设备也是遵循USB规范的,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。

​ 这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面相接口的低耦合,为系统提供更好的可扩展性和可维护性。

  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个”是不是”的is-a关系,而接口实现则是 “能不能”的has-a关系。
    • 例如:你能不能用USB进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范
    • 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范

1562216188519

1562891521094

定义格式

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

引用数据类型:数组,类,接口。

接口的声明格式

【修饰符】 interface 接口名{
    //接口的成员列表:
    // 静态常量
    // 抽象方法
    // 默认方法
    // 静态方法
    // 私有方法
}

示例代码:

interface Usb3{
    //静态常量
	long MAX_SPEED = 500*1024*1024;//500MB/s
    
    //抽象方法
	void read();
    void write();
    
    //默认方法
    public default void start(){
        System.out.println("开始");
    }
    public default void stop(){
        System.out.println("结束");
    }
    
    //静态方法
    public static void show(){
        System.out.println("USB 3.0可以同步全速地进行读写操作");
    }
}

接口的成员说明

接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。

在JDK8之前,接口中只允许出现:

(1)公共的静态的常量:其中public static final可以省略

(2)公共的抽象的方法:其中public abstract可以省略

理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现

在JDK1.8时,接口中允许声明默认方法和静态方法:

(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略

(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略

在JDK1.9时,接口又增加了:

(5)私有方法

除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要初始化。

面试题拷问?

1、为什么接口中只能声明公共的静态的常量?

因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。

例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA

​ USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA

例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。

2、为什么JDK1.8之后要允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。

静态方法:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。

默认方法:(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。

3、为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范时需要公开让大家遵守的

私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。

实现接口

接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

实现接口语法格式

【修饰符】 class 实现类  implements 接口{
	// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

【修饰符】 class 实现类 extends 父类 implements 接口{
    // 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

注意:

  1. 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。

  2. 默认方法可以选择保留,也可以重写。

    重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了

  3. 不能重写静态方法

示例代码:

class MobileHDD implements Usb3{

	//重写/实现接口的抽象方法,【必选】
	public void read() {
		System.out.println("读数据");
	}
    public void write(){
        System.out.println("写数据");
    }
	
	//重写接口的默认方法,【可选】
	//重写默认方法时,default单词去掉
	public void end(){
        System.out.println("清理硬盘中的隐藏回收站中的东西,再结束");
    }
}

如何调用对应的方法

  • 对于接口的静态方法,直接使用“接口名.”进行调用即可
    • 也只能使用“接口名.”进行调用,不能通过实现类的对象进行调用
  • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
    • 接口不能直接创建对象,只能创建实现类的对象
public class TestInteface {
	public static void main(String[] args) {
		//创建实现类对象
		MobileHDD b = new MobileHDD();
		
		//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
		b.start();
		b.read();
		b.stop();
		
		//通过接口名调用接口的静态方法
		MobileHDD.show();
	}
}

练习

1、声明一个LiveAble接口

  • 包含两个抽象方法:
    • void eat();
    • void breathe();
  • 包含默认方法 default void sleep(),实现为打印“静止不动”
  • 包含静态方法 static void drink(),实现为“喝水”

2、声明动物Animal类,实现LiveAble接口。

  • void eat();实现为“吃东西”,
  • void breathe();实现为”吸入氧气呼出二氧化碳”
  • void sleep()重写为”闭上眼睛睡觉”

3、声明植物Plant类,实现LiveAble接口。

  • void eat();实现为“吸收营养”
  • void breathe();实现为”吸入二氧化碳呼出氧气”

4、在测试类中,分别创建两个实现类的对象,调用对应的方法。通过接口名,调用静态方法

定义接口:

public interface LiveAble {
    // 定义抽象方法
    public abstract void eat();
    public abstract void breathe();
    //定义默认方法
    public default void sleep(){
    	System.out.println("静止不动");
    }
    //定义静态方法
    public static void drink(){
    	System.out.println("喝水");
    }
}

定义实现类:

public Animal implements LiveAble {
	//重写/实现接口的抽象方法
    @Override
    public void eat() {
        System.out.println("吃东西");
    }
    
    //重写/实现接口的抽象方法
    @Override
    public void breathe(){
        System.out.println("吸入氧气呼出二氧化碳");
    }
    
    //重写接口的默认方法
    @Override
    public void sleep() {
        System.out.println("闭上眼睛睡觉");
    }
}
public class Plant implements LiveAble {
	//重写/实现接口的抽象方法
    @Override
    public void eat() {
        System.out.println("吸收营养");
    }
    //重写/实现接口的抽象方法
    @Override
    public void breathe(){
        System.out.println("吸入二氧化碳呼出氧气");
    }
}

定义测试类:

public class InterfaceDemo {
    public static void main(String[] args) {
        // 创建实现类(子类)对象  
        Animal a = new Animal();
        // 调用实现后的方法
        a.eat();
        a.sleep();
        a.breathe();
        
        //创建实现类(子类)对象
        Plant p = new Plant();
        p.eat();
        p.sleep();
        p.breathe();
        
        //通过接口调用静态方法
        LiveAble.drink();
    }
}
输出结果:
吃东西
闭上眼睛睡觉
吸入氧气呼出二氧化碳
吸收营养
静止不动
吸入二氧化碳呼出氧气
喝水

接口的多实现

之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

实现格式:

【修饰符】 class 实现类  implements 接口1,接口2,接口3。。。{
	// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
    // 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次

定义多个接口:

interface A {
    public abstract void showA();
    public abstract void show();
}

interface B {
    public abstract void showB();
    public abstract void show();
}

定义实现类:

public class C implements A,B{
    @Override
    public void showA() {
        System.out.println("showA");
    }

    @Override
    public void showB() {
        System.out.println("showB");
    }

    @Override
    public void show() {
        System.out.println("show");
    }
}

练习

1、声明第一个接口Runner,包含抽象方法:void run()

2、声明第二个接口Swimming,包含抽象方法:void swim()

3、声明兔子类,实现Runner接口

4、声明乌龟类,实现Runner接口和Swimming接口

interface Runner{
	void run();
}
interface Swimming{
	void swim();
}
class Rabbit implements Runner{

	@Override
	public void run() {
		System.out.println("兔子跑得快");
	}
	
}
class Tortoise implements Runner,Swimming{

	@Override
	public void swim() {
		System.out.println("乌龟游得快");
	}

	@Override
	public void run() {
		System.out.println("乌龟跑的慢");
	}
	
}

默认方法冲突问题

亲爹优先原则

当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。代码如下:

定义接口:

interface A {
    public default void methodA(){
        System.out.println("AAAAAAAAAAAA");
    }
}

定义父类:

class D {
    public void methodA(){
        System.out.println("DDDDDDDDDDDD");
    }
}

定义子类:

class C extends D implements A {
  	// 未重写methodA方法
}
class B extends D implements A{
    //当然也可以选择重写
    public void methodA(){
        System.out.println("BBBBBBBBBBBB");
    }
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        C c = new C();
        c.methodA(); 
        
        B b = new B();
        b.methodA();
    }
}
输出结果:
DDDDDDDDDDDD
BBBBBBBBBBBB

必须做出选择

当一个类同时实现了多个接口,而多个接口中包含方法签名相同的默认方法时,怎么办呢?

无论你多难抉择,最终都是要做出选择的。代码如下:

声明接口:

interface A{
	public default void d(){
		System.out.println("今晚7点-8点陪我吃饭看电影");
	}
}
interface B{
	public default void d(){
		System.out.println("今晚7点-8点陪我逛街吃饭");
	}
}

选择保留其中一个,通过“接口名.super.方法名”的方法选择保留哪个接口的默认方法。

class C implements A,B{

	@Override
	public void d() {
		A.super.d();
	}
	
}

选择自己完全重写:

class D implements A,B{
	@Override
	public void d() {
		System.out.println("自己待着");
	}
}

接口的多继承

一个接口能继承另一个或者多个接口,接口的继承也使用 extends 关键字,子接口继承父接口的方法。

定义父接口:

interface A {
    void a();
    public default void methodA(){
        System.out.println("AAAAAAAAAAAAAAAAAAA");
    }
}

interface B {
    void b();
    public default void methodB(){
        System.out.println("BBBBBBBBBBBBBBBBBBB");
    }
}

定义子接口:

interface C extends A,B{
    @Override
    public default void methodB() {
        System.out.println("CCCCCCCCCCCCCCCCCCCC");
    }
}

小贴士:

子接口重写默认方法时,default关键字可以保留。

子类重写默认方法时,default关键字不可以保留。

class D implements C{

	@Override
	public void a() {
		System.out.println("xxxxx");
	}

	@Override
	public void b() {
		System.out.println("yyyyy");
	}
	
}
class E implements A,B,C{//效果和上面的D是等价的

	@Override
	public void b() {
		System.out.println("xxxxx");
	}

	@Override
	public void a() {
		System.out.println("yyyyy");
	}
	
}

接口与实现类对象的多态引用

实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。

public class TestInterface {
	public static void main(String[] args) {
		Flyable b = new Bird();
		b.fly();
		
		Flyable k = new Kite();
		k.fly();
	}
}
interface Flyable{
    //抽象方法
	void fly();
}
class Bird implements Flyable{

	@Override
	public void fly() {
		System.out.println("展翅高飞");
	}
	
}
class Kite implements Flyable{

	@Override
	public void fly() {
		System.out.println("别拽我,我要飞");
	}
	
}

接口面试题排错

第1题:成员变量冲突问题

1562417617254

class Base{
    int x = 1;
}
interface JieKou1{
    int x = 2;//公共的静态的常量
}
interface JieKou2{
    int x = 3;
}
class Sub extends Base implements JieKou1,JieKou2{
    public void test(){
//        System.out.println(x);//错误,模糊不清
        System.out.println(super.x);
        System.out.println(JieKou1.x);
        System.out.println(JieKou2.x);
    }
}

经典接口介绍

java.lang.Comparable

我们知道基本数据类型的数据(除boolean类型外)需要比较大小的话,之间使用比较运算符即可,但是引用数据类型是不能直接使用比较运算符来比较大小的。那么,如何解决这个问题呢?

Java给所有引用数据类型的大小比较,指定了一个标准接口,就是java.lang.Comparable接口:

package java.lang;

public interface Comparable{
    int compareTo(Object obj);
}

那么我们想要使得我们某个类的对象可以比较大小,怎么做呢?步骤:

第一步:哪个类的对象要比较大小,哪个类就实现java.lang.Comparable接口,并重写方法

  • 方法体就是你要如何比较当前对象和指定的另一个对象的大小

第二步:对象比较大小时,通过对象调用compareTo方法,根据方法的返回值决定谁大谁小。

  • this对象(调用compareTo方法的对象)大于指定对象(传入compareTo()的参数对象)返回正整数
  • this对象(调用compareTo方法的对象)小于指定对象(传入compareTo()的参数对象)返回负整数
  • this对象(调用compareTo方法的对象)等于指定对象(传入compareTo()的参数对象)返回零

代码示例:

public class TestComparable {
	public static void main(String[] args) {
		Student s1 = new Student(1,"张三",89);
		Student s2 = new Student(2,"李四",89);
		if(s1.compareTo(s2)>0){
			System.out.println("s1>s2");
		}else if(s1.compareTo(s2)<0){
			System.out.println("s1<s2");
		}else{
			System.out.println("s1 = s2");
		}
	}
}
class Student implements Comparable{
	private int id;
	private String name;
	private int score;
	
	//省略了构造器、get/set、toString等方法

	@Override
	public int compareTo(Object o) {
		//这些需要强制,将o对象向下转型为Student类型的变量,才能调用Student类中的属性
		Student stu = (Student) o;
		if(this.score != stu.score){
			return this.score - stu.score;
		}else{//成绩相同,按照学号比较大小
			return this.id - stu.id;
		}
	}
	
}
练习1:冒泡排序

声明一个Employee员工类,包含编号、姓名、薪资,实现Comparable接口,要求,按照薪资比较大小,如果薪资相同,按照编号比较大小。

声明一个测试类TestEmployee类,在main中创建Employee[]数组,长度为5,并且存储5个员工对象,现在要求用冒泡排序,实现对这个数组进行排序,遍历结果。

class Employee implements Comparable{
	private int id;
	private String name;
	private double salary;
	public Employee(int id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	public Employee() {
		super();
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}
	@Override
	public int compareTo(Object o) {
		Employee emp = (Employee) o;
		if(this.getSalary() != emp.getSalary()){
			return Double.compare(this.getSalary(), emp.getSalary());
		}
		return this.id - emp.id;
	}
}
public class TestComparable {
	public static void main(String[] args) {
		Employee[] arr = new Employee[5];
		arr[0] = new Employee(1,"张三",13000);
		arr[1] = new Employee(2,"李四",13000);
		arr[2] = new Employee(3,"王五",14000);
		arr[3] = new Employee(4,"赵六",7000);
		arr[4] = new Employee(5,"钱七",9000);
		
		//原顺序
		System.out.println("员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		//冒泡排序
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length-i; j++) {
                //因为Employee类型实现了Comparable接口,所以有compareTo()方法
				if(arr[j].compareTo(arr[j+1])>0){
					Employee temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		System.out.println("排序后员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}
练习2:自定义数组排序工具类

自定义一个数组工具类MyArrays,它包含一个静态方法,可以给任意对象数组用冒泡排序实现从小到大排序,该怎么定义这个方法呢?

class MyArrays{
	public static void sort(Object[] arr){
		//冒泡排序
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length-i; j++) {
				//将arr[j]强制为Comparable接口类型,目的是调用compareTo方法
				//当然如果数组的元素没有实现这个接口,那么将会发生ClassCastException
				Comparable c = (Comparable) arr[j];
				if(c.compareTo(arr[j+1])>0){
					Object temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
	}
    
    public static void print(Object[] arr){
        for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
    }
}

使用自定义的MyArrays数组工具类,给练习1的员工数组进行排序

public class TestComparable {
	public static void main(String[] args) {
		Employee[] arr = new Employee[5];
		arr[0] = new Employee(1,"张三",13000);
		arr[1] = new Employee(2,"李四",13000);
		arr[2] = new Employee(3,"王五",14000);
		arr[3] = new Employee(4,"赵六",7000);
		arr[4] = new Employee(5,"钱七",9000);
		
		//原顺序
		System.out.println("员工列表:");
		MyArrays.print(arr);
        
        //要求Employee类型必须实现Comparable接口,否则将发生ClassCastException异常
		MyArrays.sort(arr);
        
		System.out.println("排序后员工列表:");
		MyArrays.print(arr);
	}
}

java.util.Arrays数组工具类的public static void sort(Object[] a)就是这么实现的,只不过它使用的排序算法是效率更高快排,而不是冒泡排序,但是无论哪种排序算法,最终都要涉及到两个元素的比较大小,都需要通过元素调用compareTo()方法。

java.util.Comparator

思考:

(1)如果一个类,没有实现Comparable接口,而这个类你又不方便修改(例如:一些第三方的类,你只有.class文件,没有源文件),那么这样类的对象也要比较大小怎么办?

(2)如果一个类,实现了Comparable接口,也指定了两个对象的比较大小的规则,但是此时此刻我不想按照它预定义的方法比较大小,但是我又不能随意修改,因为会影响其他地方的使用,怎么办?

JDK在设计类库之初,也考虑到这种情况了,所以又增加了一个java.util.Comparator接口。

package java.util;

public interface Comparator{
    int compare(Object o1,Object o2);
}

那么我们想要比较某个类的两个对象的大小,怎么做呢?步骤:

第一步:编写一个类,我们称之为比较器类型,实现java.util.Comparator接口,并重写方法

  • 方法体就是你要如何指定的两个对象的大小

第二步:比较大小时,通过比较器类型的对象调用compare()方法,将要比较大小的两个对象作为compare方法的实参传入,根据方法的返回值决定谁大谁小。

  • o1对象大于o2返回正整数
  • o1对象小于o2返回负整数
  • o1对象等于o2返回零

代码示例:一个没有实现Comparable接口的学生类

class Student{
	private String name;
	private int score;
	public Student(String name, int score) {
		super();
		this.name = name;
		this.score = score;
	}
	public Student() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getScore() {
		return score;
	}
	public void setScore(int score) {
		this.score = score;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", score=" + score + "]";
	}
	
}

代码示例:定义定制比较器类

class StudentScoreCompare implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		Student s1 = (Student) o1;
		Student s2 = (Student) o2;
		return s1.getScore() - s2.getScore();
	}
	
}

代码示例:测试类

import java.util.Comparator;

public class TestComparator {
	public static void main(String[] args) {
		Student stu1 = new Student("张三",89);
		Student stu2 = new Student("李四",78);
		
		StudentScoreCompare ssc = new StudentScoreCompare();
		if(ssc.compare(stu1, stu2)>0){
			System.out.println(stu1 + ">" + stu2);
		}else if(ssc.compare(stu1, stu2)<0){
			System.out.println(stu1 + "<" + stu2);
		}else{
			System.out.println(stu1 + "=" + stu2);
		}
	}
}
练习1:冒泡排序

声明一个Employee员工类,包含编号、姓名、薪资,

声明一个测试类,在main中,创建Employee[]数组,长度为5,显示原来顺序结果

声明一个定制比较器EmpSalaryComparator,实现Comparator接口,按照薪资比较大小

声明一个定制比较器EmpIdComparator,实现Comparator接口,按照编号比较大小

在测试类中,分别用这个两个比较器对象,对数组进行排序,并显示排序后结果

员工类示例代码:

class Employee{
	private int id;
	private String name;
	private double salary;
	public Employee(int id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	public Employee() {
		super();
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}
}

员工薪资定制比较器类型:

class EmpSalaryComparator implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		Employee e1 = (Employee) o1;
		Employee e2 = (Employee) o2;
		return Double.compare(e1.getSalary(), e2.getSalary());
	}
	
}

员工编号定制比较器类型:

class EmpIdComparator implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		Employee e1 = (Employee) o1;
		Employee e2 = (Employee) o2;
		return e1.getId() - e2.getId();
	}
	
}

测试类示例代码:

import java.util.Comparator;

public class TestComparator {
	public static void main(String[] args) {
		Employee[] arr = new Employee[5];
		arr[0] = new Employee(1,"张三",13000);
		arr[1] = new Employee(3,"王五",14000);
		arr[2] = new Employee(2,"李四",13000);
		arr[3] = new Employee(4,"赵六",7000);
		arr[4] = new Employee(5,"钱七",9000);
		
		//原顺序
		System.out.println("员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		
		EmpSalaryComparator ec = new EmpSalaryComparator();
		//冒泡排序
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length-i; j++) {
				if(ec.compare(arr[j], arr[j+1])>0){
					Employee temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
		
		System.out.println("按照薪资排序后员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		
		EmpIdComparator ec2 = new EmpIdComparator();
		//冒泡排序
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length-i; j++) {
				if(ec2.compare(arr[j], arr[j+1])>0){
					Employee temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
				
		System.out.println("按照编号排序后员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}
练习2:自定义数组排序工具类

自定义一个数组工具类MyArrays,它包含一个静态方法,可以给任意对象数组用冒泡排序实现从小到大排序,该怎么定义这个方法呢?

class MyArrays{
	public static void sort(Object[] arr,Comparator c){
		//冒泡排序
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length-i; j++) {
				//这里不需要强制类型转换
				if(c.compare(arr[j], arr[j+1])>0){
					Object temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1] = temp;
				}
			}
		}
	}
	
    public static void print(Object[] arr){
 		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}       
    }
}

用新工具类,简化练习1测试类的代码

public class TestComparator {
	public static void main(String[] args) {
		Employee[] arr = new Employee[5];
		arr[0] = new Employee(1,"张三",13000);
		arr[1] = new Employee(3,"王五",14000);
		arr[2] = new Employee(2,"李四",13000);
		arr[3] = new Employee(4,"赵六",7000);
		arr[4] = new Employee(5,"钱七",9000);
		
		//原顺序
		System.out.println("员工列表:");
		MyArrays.print(arr);
		
		EmpSalaryComparator ec = new EmpSalaryComparator();
		MyArrays.sort(arr, ec);
		
		System.out.println("按照薪资排序后员工列表:");
		MyArrays.print(arr);
		
		EmpIdComparator ec2 = new EmpIdComparator();
		MyArrays.sort(arr, ec2);
				
		System.out.println("按照编号排序后员工列表:");
		MyArrays.print(arr);
	}
}

java.util.Arrays数组工具类的public static void sort(T[] a, Comparator<? super T> c)就是这做的

java.lang.Cloneable

在java.lang.Object类中有一个方法:

protected Object clone()throws CloneNotSupportedException 

所有类型都可以重写这个方法,它是获取一个对象的克隆体对象用的,就是造一个和当前对象各种属性值一模一样的对象。当然地址肯定不同。

我们在重写这个方法后时,调用super.clone(),发现报异常CloneNotSupportedException,因为我们没有实现java.lang.Cloneable接口。

class Teacher implements Cloneable{
	private int id;
	private String name;
	public Teacher(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public Teacher() {
		super();
	}
	public int getId() {
		return id;
	}
	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 "Teacher [id=" + id + ", name=" + name + "]";
	}
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Teacher other = (Teacher) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}
public class TestClonable {
	public static void main(String[] args) throws CloneNotSupportedException {
		Teacher src = new Teacher(1,"高老师");
		Object clone = src.clone();
		System.out.println(clone);
		System.out.println(src == clone);
		System.out.println(src.equals(clone));
	}
}

内部类

概述

1、什么是内部类?

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

2、为什么要声明内部类呢?

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。

而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员。

3、内部类都有哪些形式?

根据内部类声明的位置(如同变量的分类),我们可以分为:

(1)成员内部类:

  • 静态成员内部类
  • 非静态成员内部类

(2)局部内部类

  • 有名字的局部内部类
  • 匿名的内部类

静态内部类

语法格式:

【修饰符】 class 外部类{
    【其他修饰符】 static class 内部类{
    }
}

静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
    • 可以使用abstract修饰,因此它也可以被其他类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
    • 外部类只允许public或缺省的
  • 可以在静态内部类中使用外部类的静态成员
    • 在静态内部类中不能使用外部类的非静态成员哦
  • 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象
  • 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名.”进行区别

示例代码:

public class TestInner{
    public static void main(String[] args){
    	Outer.Inner in= new Outer.Inner();
    	in.inMethod();
    	
    	Outer.Inner.inTest();
        
        Outer.Inner.inFun(3);
    }
}

class Outer{
	private static int a = 1;
	private int b = 2;
	protected static class Inner{
		static int d = 4;//可以
		void inMethod(){
			System.out.println("out.a = " + a);
//			System.out.println("out.b = " + b);//错误的
		}
		static void inTest(){
			System.out.println("out.a = " + a);
		}
        static void inFun(int a){
			System.out.println("out.a = " + Outer.a);
            System.out.println("local.a = " + a);
		}
	}
}

其实严格的讲(在James Gosling等人编著的《The Java Language Specification》)静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已。所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。例如:Map.Entry。

非静态成员内部类

语法格式:

【修饰符】 class 外部类{
    【修饰符】 class 内部类{
    }
}

非静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量
    • 可以使用abstract修饰,因此它也可以被其他类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
    • 外部类只允许public或缺省的
  • 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的

  • 在外部类的静态成员中不可以使用非静态内部类哦

    • 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
  • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象
    • 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象

示例代码:

public class TestInner{
    public static void main(String[] args){
    	Outer out = new Outer();
    	Outer.Inner in= out.new Inner();
    	in.inMethod();
    	
    	Outer.Inner inner = out.getInner();
    	inner.inMethod();
    }
}
class Father{
	protected static int c = 3;
}
class Outer{
	private static int a = 1;
	private int b = 2;
	protected class Inner extends Father{
//		static int d = 4;//错误
		int b = 5;
		void inMethod(){
			System.out.println("out.a = " + a);
			System.out.println("out.b = " + Outer.this.b);
			System.out.println("in.b = " + b);
			System.out.println("father.c = " + c);
		}
	}
	
	public static void outMethod(){
//		Inner in = new Inner();//错误的
	}
	public Inner getInner(){
		return new Inner();
	}
}

练习1:语法练习题

声明一个身体Body类,包含一个私有的boolean类型的属性live,初始化为true,表示活着。属性私有化,提供get/set方法。

声明一个身体Body的内部类Heart,包含void beat()方法,当live为true时,打印“心脏在跳动”,否则打印“心脏停止跳动”。因为Heart只为外部类Body服务,而又具有自己的方法,属性等,而且这里应该是有Body实体存在的情况下才能有Heart实体,所以这里把Heart声明为非静态内部类。

声明一个测试类,在测试类的主方法中,创建身体和心脏的对象,调用心脏对象的beat()方法,然后调用身体对象的setLive()方法,设置为false后,再调用心脏对象的beat()方法查看结果。

public class Person {
    private  boolean live = true;
    class Heart {
        public void beat() {
            // 直接访问外部类成员
            if (live) {
                System.out.println("心脏在跳动");
            } else {
                System.out.println("心脏不跳了");
            }
        }
    }

    public boolean isLive() {
        return live;
    }

    public void setLive(boolean live) {
        this.live = live;
    }

}
public class InnerDemo {
    public static void main(String[] args) {
        // 创建外部类对象 
        Person p  = new Person();
        // 创建内部类对象
        Heart heart = p.new Heart();

        // 调用内部类方法
        heart.beat();
        // 调用外部类方法
        p.setLive(false);
        // 调用内部类方法
        heart.beat();
    }
}
输出结果:
心脏在跳动
心脏不跳了

练习2:简单面试题

判断如下代码的运行结果:

public class Test{
	public Test(){
		Inner s1 = new Inner();
		s1.a = 10;
		Inner s2 = new Inner();
		s2.a = 20;
		Test.Inner s3 = new Test.Inner();
		System.out.println(s3.a);
	}
	class Inner{
		public int a = 5;
	}
	public static void main(String[] args) {
		Test t = new Test();
		Inner r = t.new Inner();
		System.out.println(r.a);
	}
}

局部内部类

语法格式:

【修饰符】 class 外部类{
    【修饰符】 返回值类型  方法名(【形参列表】){final/abstractclass 内部类{
    	}
    }    
}

局部内部类的特点:

  • 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的静态常量
    • 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
      • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
  • 和成员内部类不同的是,它前面不能有权限修饰符等
  • 局部内部类如同局部变量一样,有作用域
  • 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法
  • 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
    • JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final

示例代码:

class Outer{
	private static int a = 1;
	private int b = 2;
	
	public static void outMethod(){
		final int c = 3;
		class Inner{
			public void inMethod(){
				System.out.println("out.a = " + a);
//				System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
				System.out.println("out.local.c = " + c);
			}
		}
		
		Inner in = new Inner();
		in.inMethod();
	}
	
	public void outTest(){
		final int c = 3;
		class Inner{
			public void inMethod(){
				System.out.println("out.a = " + a);
				System.out.println("out.b = " + b);//可以,因为outTest是飞静态的
				System.out.println("method.c = " + c);
			}
		}
		
		Inner in = new Inner();
		in.inMethod();
	}
	
}

思考

为什么在局部内部类中使用外部类方法的局部变量要加final呢?

public class TestInner{
	public static void main(String[] args) {
		A obj = Outer.method();
		//因为如果c不是final的,那么method方法执行完,method的栈空间就释放了,那么c也就消失了
		obj.a();//这里打印c就没有中可取了,所以把c声明为常量,存储在方法区中
	}
}

interface A{
	void a();
}
class Outer{
	public static A method(){
		final int c = 3;
		class Sub implements A{
			@Override
			public void a() {
				System.out.println("method.c = " + c);
			}
		}
		return new Sub();
	}
}

匿名内部类

引入

当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?

(1)编写类,继承这个父类或实现这个接口

(2)重写父类或父接口的方法

(3)创建这个子类或实现类的对象

例如:

public interface Runnable{
    public abstract void run();
}
//声明接口实现类
public class MyRunnable implements Runnable{
    public void run(){
        while(true){
            System.out.println("大家注意安全");
            try
            	Thread.sleep(1000);
            }catch(Exception e){                
            }
        }
    }
}
public class Test{    public static void main(String[] args){        //如果MyRunnable类只是在这里使用一次,并且只创建它的一个对象        //分开两个.java源文件,反而不好维护        Runnable target = new MyRunnable();        Thread t = new Thread("安全提示线程",target);        t.start();    }}

这里,因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。

可以修改为如下形式:

public class Test{
    public static void main(String[] args){
        //MyRunnable类只是在这里使用一次,并且只创建它的一个对象,那么这些写代码更紧凑,更好维护
        Runnable target = new Runnable(){
            public void run(){
                while(true){
                    System.out.println("大家注意安全");
                    try
                        Thread.sleep(1000);
                    }catch(Exception e){                
                    }
                }
            }
        };
        Thread t = new Thread("安全提示线程",target);
        t.start();
    }
}

语法格式

new 父类(【实参列表】){
    重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
    重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造

匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。

注意:

匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:

  • 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
  • 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final

思考:这个对象能做什么呢?

答:(1)调用某个方法(2)赋值给父类/父接口的变量,通过多态引用使用这个对象(3)作为某个方法调用的实参

使用方式一:匿名内部类的对象直接调用方法

interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	}.a();
    }
}
class B{
	public void b(){
		System.out.println("bbbb");
	}
}
public class Test{
    public static void main(String[] args){
    	new B(){
    		public void b(){
    			System.out.println("ccccc");
    		}
    	}.b();
    	
    }
}

使用方式二:通过父类或父接口的变量多态引用匿名内部类的对象

interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	A obj = new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	};
    	obj.a();
    }
}
class B{
	public void b(){
		System.out.println("bbbb");
	}
}
public class Test{
    public static void main(String[] args){
    	B obj = new B(){
    		public void b(){
    			System.out.println("ccccc");
    		}
    	};
    	obj.b();
    }
}

使用方式三:匿名内部类的对象作为实参

interface A{
	void method();
}
public class Test{
    public static void test(A a){
    	a.method();
    }
    
    public static void main(String[] args){
    	test(new A(){

			@Override
			public void method() {
				System.out.println("aaaa");
			}
    		
    	});
    }   
}

练习

练习1

声明一个Employee员工类,包含编号、姓名、薪资,

声明一个测试类,在main中,创建Employee[]数组,长度为5,显示原来顺序结果

调用java.util.Arrays数组工具类的排序方法public static void sort(Object[] a, Comparator c)对数组的元素进行排序,用匿名内部类的对象给c形参传入按照薪资比较大小的定制比较器对象。并显示排序后结果

调用java.util.Arrays数组工具类的排序方法public static void sort(Object[] a, Comparator c)对数组的元素进行排序,用匿名内部类的对象给c形参传入按照编号比较大小的定制比较器对象。并显示排序后结果

员工类示例代码:

class Employee{
	private int id;
	private String name;
	private double salary;
	public Employee(int id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	public Employee() {
		super();
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}
}

测试类:

public class TestInner {
	public static void main(String[] args) {
		Employee[] arr = new Employee[5];
		arr[0] = new Employee(1,"张三",13000);
		arr[1] = new Employee(3,"王五",14000);
		arr[2] = new Employee(2,"李四",13000);
		arr[3] = new Employee(4,"赵六",7000);
		arr[4] = new Employee(5,"钱七",9000);
		
		//原顺序
		System.out.println("员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		
		Arrays.sort(arr, new Comparator() {
			@Override
			public int compare(Object o1, Object o2) {
				Employee e1 = (Employee) o1;
				Employee e2 = (Employee) o2;
				return Double.compare(e1.getSalary(), e2.getSalary());
			}
		});
		
		System.out.println("按照薪资排序后员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		
		Arrays.sort(arr, new Comparator() {
			@Override
			public int compare(Object o1, Object o2) {
				Employee e1 = (Employee) o1;
				Employee e2 = (Employee) o2;
				return e1.getId() - e2.getId();
			}
		});
				
		System.out.println("按照编号排序后员工列表:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}
练习2

(1)声明一个抽象类Father,包含抽象方法:public abstract void method();
(2)用匿名内部类继承Father,并重写抽象方法,打印“hello baby”
并调用子类对象的method方法

public abstract class Father{
	public abstract void method();
}
public class TestExer1 {
	public static void main(String[] args) {
		new Father(){

			@Override
			public void method() {
				System.out.println("hello 孩子");
			}
			
		}.method();
	}
}
练习3

(1)声明一个员工类Triangle三角形,有属性:a,b,c表示三条边
(2)在测试类中创建Triangle数组
(3)分别调用Arrays.sort(数组,Comparator),用匿名内部类实现按照编号周长排列
(4)分别调用Arrays.sort(数组,Comparator),用匿名内部类实现按照薪资面积排列

public class Triangle {
	private double a;
	private double b;
	private double c;
	public Triangle(double a, double b, double c) {
		super();
		this.a = a;
		this.b = b;
		this.c = c;
	}
	public Triangle() {
		super();
	}
	public double getA() {
		return a;
	}
	public void setA(double a) {
		this.a = a;
	}
	public double getB() {
		return b;
	}
	public void setB(double b) {
		this.b = b;
	}
	public double getC() {
		return c;
	}
	public void setC(double c) {
		this.c = c;
	}
	@Override
	public String toString() {
		return "Triangle [a=" + a + ", b=" + b + ", c=" + c + "]";
	}
	public double getPerimeter(){
		return a+b+c;
	}
	public double getArea(){
		double p = getPerimeter()/2;
		return Math.sqrt(p*(p-a)*(p-b)*(p-c));
	}
}
public class TestExer2 {
	public static void main(String[] args) {
		Triangle[] arr = new Triangle[3];
		arr[0]  = new Triangle(6, 1, 6);
		arr[1]  = new Triangle(3, 4, 5);
		arr[2]  = new Triangle(6, 6, 6);
		
		System.out.println("原来的顺序:");
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		System.out.println("--------------------");
		System.out.println("按照周长排序:");
		Arrays.sort(arr, new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				Triangle t1 = (Triangle) o1;
				Triangle t2 = (Triangle) o2;
				return Double.compare(t1.getPerimeter(), t2.getPerimeter());
			}
		});
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
		System.out.println("--------------------");
		System.out.println("按照面积排序:");
		Arrays.sort(arr, new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				Triangle t1 = (Triangle) o1;
				Triangle t2 = (Triangle) o2;
				return Double.compare(t1.getArea(), t2.getArea());
			}
		});
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}
练习4

1、声明一个接口:Predicate接口,包含public abstract boolean test(Object obj);抽象方法
2、声明一个员工类:Employee,有属性:编号、姓名、年龄、薪资
3、声明一个员工管理类:EmployeeService,
(1)包含Employee[] arr,并在EmployeeService构造器中,创建数组,并初始化数组,例如:
arr = new Employee[5];
arr[0] = new Employee(4, “李四”, 24, 24000);
arr[1] = new Employee(3, “张三”, 23, 13000);
arr[2] = new Employee(5, “王五”, 25, 15000);
arr[3] = new Employee(1, “赵六”, 27, 17000);
arr[4] = new Employee(2, “钱七”, 16, 6000);

(2)包含public Employee[] get(Predicate p){
Employee[] result = new Employee[arr.length];
int total = 0;
for(int i=0; i<arr.length; i++){
if(p.test(arr[i]){
result[total++] = arr[i];
}
}
return Arrays.copyOf(result,total);
}
这个方法的作用,就是用于在arr数组中筛选满足条件的元素
4、在测试类中,创建EmployeeService对象,调用get(Predicate p)方法,通过匿名内部类的对象给形参p赋值,
分别获取:
(1)所有员工对象
(2)所有年龄超过25的员工
(3)所有薪资高于15000的员工
(4)所有编号是偶数的员工
(5)名字是“张三”的员工
(6)年龄超过25,薪资高于15000的员工

public interface Predicate {
	public abstract boolean test(Object obj);
}
public class Employee{
	private int id;
	private String name;
	private int age;
	private double salary;
	public Employee() {
		super();
	}
	public Employee(int id, String name, int age, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
		this.salary = salary;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	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 "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
	}
}
public class EmployeeService {
	private Employee[] arr;

	public EmployeeService() {
		arr = new Employee[5];
		arr[0] = new Employee(4, "李四", 24, 24000);
		arr[1] = new Employee(3, "张三", 23, 13000);
		arr[2] = new Employee(5, "王五", 25, 15000);
		arr[3] = new Employee(1, "赵六", 27, 17000);
		arr[4] = new Employee(2, "钱七", 16, 6000);
	}
	public Employee[] get(Predicate p){
		Employee[] result = new Employee[arr.length] ;
		int total = 0;
		for (int i = 0; i < arr.length; i++) {
			if(p.test(arr[i])){
				result[total++] = arr[i];
			}
		}
		return Arrays.copyOf(result, total);
	}
}
public class TestExer5 {
	public static void main(String[] args) {
		EmployeeService es = new EmployeeService();
		
		//(1)所有员工对象
		Employee[] employees = es.get(new Predicate(){

			@Override
			public boolean test(Object obj) {
				return true;
			}
			
		});
		for (int i = 0; i < employees.length; i++) {
			System.out.println(employees[i]);
		}
		System.out.println("============================");
//		(2)所有年龄超过25的员工
		employees = es.get(new Predicate(){

			@Override
			public boolean test(Object obj) {
				Employee emp = (Employee) obj;
				return emp.getAge()>25;
			}
			
		});
		for (int i = 0; i < employees.length; i++) {
			System.out.println(employees[i]);
		}
        //....
	}
}

注解

什么是注解

注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:

@SuppressWarnings(value=”unchecked”)
@Override
@Deprecated
@Test
@author
@param
....

注解Annotation是从JDK5.0开始引入。

虽然说注解也是一种注释,因为它们都不会改变程序原有的逻辑,只是对程序增加了某些注释性信息。不过它又不同于单行注释和多行注释,对于单行注释和多行注释是给程序员看的,而注解是可以被编译器或其他程序读取的一种注释,程序还可以根据注解的不同,做出相应的处理。所以注解是插入到代码中以便有工具可以对它们进行处理的标签。

一个完整的注解有三个部分:

  • 注解的声明:就如同类、方法、变量等一样,需要先声明后使用
  • 注解的使用:用于注解在包、类、方法、属性、构造、局部变量等上面的10个位置中一个或多个位置
  • 注解的读取:有一段专门用来读取这些使用的注解,然后根据注解信息作出相应的处理,这段程序称为注解处理流程,这也是注解区别与普通注释最大的不同。

注解和之前的类和方法不一样。之前的类和方法只有声明和使用两个部分。类声明完了,作用和功能就定了,调用时根据声明执行即可。而注解的声明只是说明了该注解的使用格式,注解的作用由读取该注解的程序决定。

示例说明:

image-20200307194703453

image-20200307200134140

image-20200307201107365

而我们平时使用的注解,要么是JRE核心类库中声明的,要么是某个框架(例如JUunit框架,Spring框架等)声明的,如果是JRE类库中声明的注解,JDK中都会提供该注解的读取程序,要么是在编译中,要么在某个其他的地方。而框架中声明注解,读取该注解的程序在框架中定义,因此每一个注解的意义由框架(例如JUunit框架,Spring框架等)决定。

系统预定义的三个最基本的注解

@Override

​ 用于检测被修饰的方法为有效的重写方法,如果不是,则报编译错误!

​ 只能标记在方法上。

​ 它会被编译器程序读取。

@Deprecated

​ 用于表示被标记的数据已经过时,不建议使用。

​ 可以用于修饰 属性、方法、构造、类、包、局部变量、参数。

​ 它会被编译器程序读取。

@SuppressWarnings

​ 抑制编译警告。

​ 可以用于修饰类、属性、方法、构造、局部变量、参数

​ 它会被编译器程序读取。

示例代码:

public class TestAnnotation {
	@SuppressWarnings({"unused","rawtypes", "unchecked"})
	public static void main(String[] args) {
		
		int i;
	
		List list = new ArrayList();
		list.add("");
		list.add(123);
		list.add("");
		
		Father f = new Son();
		f.show();
		f.methodOl();
	}

}


class Father{
	@Deprecated
	public void show() {
		
	}
	public void methodOl() {
		System.out.println("Father Method");
	}
	public void print1n(){
		System.out.println("Father Method");
	}
	public int sum(int... nums){
		int sum = 0;
		for (int i = 0; i < nums.length; i++) {
			sum += nums[i];
		}
		return sum;
	}
}

class Son extends Father{
	
/*	@Override
	public void method01() {
		System.out.println("Son Method");
	}
	
	@Override
	public void println(){
		System.out.println("Father Method");
	}
	
	@Override
	public long sum(int[] nums){
		int sum = 0;
		for (int i = 0; i < nums.length; i++) {
			sum += nums[i];
		}
		return sum;
	}*/
}

Java中文档注释

  • @author 标明开发该类模块的作者,多个作者之间使用,分割
  • @version 标明该类模块的版本
  • @see 参考转向,也就是相关主题
  • @since 从哪个版本开始增加的
  • @param 对方法中某参数的说明,如果没有参数就不能写
  • @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
  • @throws/@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
    • 其中 @param @return 和 @exception 这三个标记都是只用于方法的。
    • @param的格式要求:@param 形参名 形参类型 形参说明
    • @return 的格式要求:@return 返回值类型 返回值说明
    • @exception 的格式要求:@exception 异常类型 异常说明
    • @param和@exception可以并列多个

javadoc.exe就是这些注解的信息处理流程。

示例代码:

/**
 * 
 * @author Irene
 *
 */
public class TestAnnotation2 {
	
	/**
	 * 这是Java的主方法,是Java程序的入口
	 * @param args String[] 命令行参数,使用java命令时,在后面传入参数,例如
	 * 	java 类名   参数1  参数2 ....
	 */
	public static void main(String[] args) {
		
	}
	
	/**
	 * 这是一个求两个整数中最大值的方法
	 * @param a int 其中一个整数
	 * @param b int 另一个整数
	 * @return int 返回最大值
	 */
	public static int getMax(int a, int b){
		return a>b?a:b;
	}
	
	/**
	 * 这是复制一个文件的方法
	 * @param src String 源文件
	 * @param dest  String 目标文件
	 * @throws FileNotFoundException 当源文件找不到时会抛出该异常
	 */
	public static void copyFile(String src, String dest) throws FileNotFoundException{
		FileInputStream fis = new FileInputStream(src);
		//..
	}
	
	/**
	 * 
	 */
	public void println(){
		
	}
}

注释与代码要一致,如果不一致,会误导别人或自己

eclipse中导出javadoc

1576665188851

1576665298238

1576665309340

如果导出时有乱码问题,可以在上述窗口下面按next到最后一步通过增加Javadoc的额外参数选项来指定字符编码再导出:

-docencoding UTF-8
-encoding UTF-8
-charset UTF-8

1576665525558

1576665321307

1576665331437

idea中导出javadoc

1576467074566

img

JUnit单元测试

JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),供Java开发人员编写单元测试之用。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

要使用JUnit,必须在项目的编译路径中必须引入JUnit的库,即相关的.class文件组成的jar包。如何把JUnit的jar添加到编译路径如图所示:

在eclipse中截图如下:

方式一:

1562474605131

1562474620088

1562474639231

1562474653799

1562474692691

方式二:

在@Test后面按Ctrl + 1,在选择Add JUnit 4 library to the build path

1576580402867

1576580476913

在idea中截图如下:

方式一:指定本地jar目录

单击工具栏的1576580533760打开项目设置

1576584674884

1576584719209

注意:如上操作需要提前下载,并将JUnit的相关jar放到当前模块的libs文件夹中。

1576584781088

方式二:指定Marven仓库

在@Test后面按Alt + 回车,选择Add ‘JUnit4’ to classpath即可

1576580013065

1576580073306

注意:如果Maven的本地仓库(例如:C:\Users\Irene\.m2)中没有则需要联网从Maven的中央仓库中下载。

1576580095402

  • 首先使用JUnit测试的类必须是public的。需要测试的方法都必须是public,无参,无返回值。
  • @Test:标记在非静态的测试方法上。只有标记@Test的方法才能被作为一个测试方法单独测试。一个类中可以有多个@Test标记的方法。运行时如果只想运行其中一个@Test标记的方法,那么选择这个方法名,然后单独运行,否则整个类的所有标记了@Test的方法都会被执行,而且执行顺序不可控

@Test注解会被JUnit框架读取,并处理。

示例代码:

package com.atguigu.annotation;

import java.util.Arrays;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestJUnit {
	private static Object[] array;
	private static int total;

	@Test
	public void delete(){
		//从数组中删除一个元素
		System.out.println("delete");
		System.arraycopy(array, 1, array, 0, 2);
		array[--total]=null;
        System.out.println(Arrays.toString(array));
	}
	@Test
	public void tadd(){
		//往数组中存储三个元素
		System.out.println("add");
		array[total++] = "hello";
		array[total++] = "world";
		array[total++] = "java";
        System.out.println(Arrays.toString(array));
	}
	
}