Java基础 我的笔记

319 阅读54分钟

2022年9月5日

源自B站尚硅谷课程,个人笔记。

第一章 Java语言概述与课程流程

Java基础知识图谱(来自课程视频截图) 图片.png

1.1 软件开发介绍

我们每天常常使用各种软件。软件可以分为系统软件(操作系统里的软件)与应用软件(我们日常使用的软件)。 人机交互方式有图形化界面命令行方式。前者简单直观,易于操作。后者需要有一个控制台,并且还需要输入特定的指令。类似DOS系统。

1.2 常用DOS(windows命令行)指令

  • 直接输入盘符,如 D: 即可以进入D盘
  • dir 显示当前目录内文件目录
  • md 文件夹名字 创建文件夹
  • cd 目录 进入相应的文件目录 如 cd D:\我的文件
  • cd .. 退回到上一级文件夹中
  • cd/ 直接回到盘符根目录
  • del 文件名 删除文件 del *.txt 删除所有以 .txt 结尾的文件,
  • rd 文件夹名 删除相应的文件夹。(直接使用时必须确保该文件夹内是空的。)
  • del 文件夹名 删除相应的文件夹。
  • 命令行中按下上下键可以选择最近输入的指令。

1.3 计算机编程软件介绍

如果人要与计算机交流,那么需要一种语言。我们可以学习计算机语言来操作电脑。计算机语言有很多种,如 C 、 C++ 、 Java 、 PHP 、 Kotlin 、Python 、 Scala等。

计算机语言的历程

  • 第一代语言:机器语言。指令以二进制代码的形式存在。
  • 第二代语言:汇编语言。使用助记符表示一条机器指令。
  • 第三代语言:高级语言。
    • C等面向对象的语言
    • C++:面向对象/面相过程
    • Java是跨平台的纯面向对象的语言。 后台开发常用的语言:Java、PHP、Python、Go、Node.js

1.4 Java语言概述

Java技术体系平台

  • JavaSE标准版
  • JavaEE企业版
  • JavaME小型版

Java语言的特点

  • 面向对象
    • 两个概念:类、对象
    • 三大特性:封装、继承、多态
  • 健壮性
  • 跨平台性
    • 原理:JVM。不同操作系统对应有不同的JVM。有了JVM,同一个Java程序可以在不同操作系统中运行。

Java语言的运行机制与运行过程

JAVA两种核心机制

  • Java虚拟机(JVM)
  • 垃圾收集机制。不同于C语言,Java可以自动回收存储空间。但是,这不意味着Java程序不会出现内存泄漏和内存溢出的问题。

1.5 Java环境的搭建

什么是JDK

JDK是Java开发工具包。其中包括了JRE(Java运行环境。其中有包含了JVM与JavaSE标准类库)。 JDK = JRE + Java的开发工具(javac.exe、java.exe、javadoc.exe……) JRE = JVM + Java核心类库

安装JDK

笔者使用的是Geany编辑器。系统为64位。下面的安装方法仅供参考。

  1. 搜索JDK,进入官网下载。(笔者找到的官网链接是: www.oracle.com/java/techno…图片.png
  2. 目前笔者安装的是Java18。页面下滑,找到“X64 Installer”,即64位安装包。 图片.png
  3. 打开安装程序,点击下一步。图片.png
  4. 选择安装目录。笔者选择的是默认目录。可以装到其他的目录中,但是安装的目录名必须是英文,且不能包含空格。图片.png
  5. 接下来会显示安装进度条。安装后会显示如下界面。关闭即可。图片.png 至此,安装结束。但是需要配置环境变量才能继续编写程序。

环境变量设置

笔者安装的Java18安装后会自行配置相应的环境变量。其他版本可以参考课程视频的方法。 在命令提示符cmd中输入javac,如果显示下面的信息,说明环境变量基本配置成功了。 图片.png 在Java安 关于教程中环境变量JAVA_HOME的配置,详情见教程。

1.6 第一个Java程序

记事本写个小程序

Java程序代码的后缀是.java结尾的。 Java运行流程:

graph LR
a0[.java源文件]--javac.exe编译-->a1[.class字节码文件]
a1--java.exe运行-->a2[运行]

(注:这个流程图的markdown教程:zhuanlan.zhihu.com/p/340853710) 小代码

class HelloChina{

}

首先进入cmd,输入命令进入源文件所在目录,输入指令javac 0001_HelloWorld.java来使用javac对源文件0001_HelloWorld.java进行编译,之后会生成HelloWorld.class的字节码文件。这个文件的文件名是我们刚刚在代码中定义的类名。之后输入java HelloWorld运行,就会出现报错:错误: 在类 HelloChina 中找不到 main(String[]) 方法。重新写源文件:

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

更改源文件后,需要重新编译。运行后空空如也。之后再加上代码,变为:

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

编译,运行,成功输出。注意,java代码严格区分大小写。

Geany编写并运行程序

由于Java18安装后会自动配置好环境变量,所以使用Geany直接打开源文件,并且点击编译、生成后,点击运行,即可运行程序。

1.7 注释

单行注释与多行注释

  1. java规范了许多注释。单行注释。多行注释。
  2. 注释的作用:
    1. 对程序进行解释说明,方便自己,方便他人,增强可读性。
    2. 可以对代码进行调试。比如可以把要进行调试的代码注释掉进行测试。
  3. 特点:注释的内容不参与编译,生成的.class文件中不包含注释的信息。
//0001_Conmment
class HelloJava{
	//如下的main方法是程序的入口
	/*
	 * 多行注释
	 * main的格式是固定的!
	 */
	public static void main(String[] args){
		//这是一个单行注释
		System.out.println("HelloWorld!");
	}
}

多行注释是不能嵌套使用的。

文档注释

格式:/** 内容,可换行 */ 文档注释的内容可以被JDK中的javadoc所解析,生成一套网页格式的文件来说明该文档。 例如在文件里写下以下内容:

//HalloJava.java
/**
文档注释
@author XueXiJava
@version v999.99
*/
public class HalloJava{
	//单行注释
	/*
	 * 多行注释
	 * 类 HalloJava 是公共的, 应在名为 HalloJava.java 的文件中定义
	 */
	/**
	如下的方式是main(),作用:程序的入口。
	*/
	public static void main(String[] args){
		System.out.println("HelloWorld!");
	}
}

使用javadoc解析文件:javadoc -d 名字 -author -version 0001_Conmment.java 图片.png 在生成的文件夹里,会有下列文件: 图片.png 打开其中的index.html,再在网页中打开文件,就可以显示下列信息: 图片.png

1.8 JavaAPI文档

JavaSE18文档(页面右上角可以更改中文):docs.oracle.com/en/java/jav…

总结:

  1. 程序的编写

  • 编写:我们将编写的java文件保存在以“.java”为结尾的源文件中。

  • 编译:使用javac.exe编译源文件,生成字节码文件。命令:javac 文件名

  • 运行:使用java.exe命令执行字节码文件。命令:java 类名

  1. 在一个java源文件中可以含有多个类,但只能有一个类声明为public。而且声明为public的类名必须与源文件名相同。
  2. 程序的入口是main()方法,格式是固定的。
  3. 输出语句 System.out.println("HelloWorld!"); 先输出后换行。 System.out.print("NiHAo!"); 不换行。
  4. 每个执行语句都以分号“;”结束。
  5. 编译的时候会生成一个或者多个字节码文件,其文件名与对应的类名相同。

Java基本语法(上)

2.1 关键字与保留字

Java编程时保留的关键字。

2.2 标识符

  • 标识符:变量、类、方法等起的名字
  • 命名规则:构成:字母、数字、“_”、美元符号。数字不能开头。名字不能用关键字起名。区分大小写,长度无限制。不能包含空格。(不遵守命名规则,编译不通过!)
  • 命名规范:(通常的编程习惯)
    • 包名:全部小写 xxxyyyzzz
    • 类名:单词首字母大写 XxxYyyZzz
    • 变量、方法:除第一个单词外,首字母大写 xxYyyZzz
    • 常量:全部大写,单词之间下划线连接 XXX_YYY_ZZZ
  • 注意:
    1. 命名时最好明了,“见名知意”。
    2. Java使用Unicode字符集,可以包含中文命名,但不推荐。

2.3 变量

  • 概念:
    • 内存中的一个存储区域
  • 结构:
    • 类型(字符串、整形、浮点型……)
    • 变量名
    • 存储的值
  • 使用:
    • 定义变量的格式:数据类型 变量名 = 变量值;
  • 作用:
    • 在内存中保存数据
  • 注意:
    • 变量必须先声明,后使用。
    • 变量都定义在其作用域内,在作用域内有效,作用域外无效,
    • 同一个作用域内不能声明两个同名变量。
class bianLiang2 {
	public static void main(String[] args){
		//~ 定义
		int myAge=12;
		//~ 使用
		System.out.println(myAge);
		
		//~ System.out.println(myNumber);
		//~ 上一条语句无法执行
		//~ 使用myNumber之前并没有定义过变量
		
		//~ 声明
		int myNumber;
		
		//~ System.out.println(myNumber);
		//~ 此处变量没有被赋值过,编译会报错
		
		//~ 赋值
		myNumber=1123;
		System.out.println(myNumber);
		
		//~ int myNumber=1012;
		//~ 不能重复定义
	}
	
	public void mythod(){
		//~ System.out.println(myNumber);
		//~ 不可行
	}
}

2.4 Java中的数据类型

数据类型

图片.png

变量在声明的位置

成员变量、局部变量……

整形

类型占用存储空间范围
byte一个字节=8位-128到127
short2字节2152151-2^{15}到2^{15}-1
int4字节2312311-2^{31}到2^{31}-1 (大概21亿)
long8字节2632631-2^{63}到2^{63}-1

浮点型

类型占用存储空间范围
float4字节单精度
double8字节双精度

float表示的值比double还要大。 float变量定义时要以"f"或"F"结尾。 通常用double

字符型

  • char (1字符=2字节)。
  • 在计算机中,字符都是以二进制的形式存储。字符集可以做到二者的对应。
  • ASCII是常用的字符集。总共128个符号。英语基本的字符集。
  • Unicode--UTF-8 更大的字符集(包括汉字)

布尔类型

  • 只能有两个值:truefalse
  • 判断时候用
class test1{
	public static void main(String[] args){
		byte a1=-1;
		byte a2=-128;
		short s1=112;
		int i=1234;
		long ll=123123123L;
		//~ 定义long时,数据要以"l"或"L"结尾。
		
		char c1='a';
		char c2='B';
		System.out.println(c1);
		char c3='中';
		System.out.println(c3);
		//~ char c3='AB';
		//~ 不可行 必须是俺单个字符
		char c5='\n';
		System.out.print("hello"+c5);
		System.out.println(c1+c2);
		char c6='\u0043';
		System.out.println(c6);
		boolean buer=false;
		System.out.println(buer);
		if(buer){
			System.out.println(buer);
		}
		else{
			System.out.println("布尔类型\\n 注意,这里没有换行。");
		}
	}
}

2.5 基本数据类型之间的运算

(1)自动类型提升

  • byte-->short-->int-->long-->float-->double
  • 小容量类型与大容量类型运算,结果提升到大类型
  • byte/short/char三种类型的变量做运算时,结果为int类型
class yunsuan{
	public static void main(String[] args){
		byte b1=2;
		int i1=129;
		//~ byte b2=b1+i1;
		//~ 上一条语句不通过
		//~ 相加为范围比较大的类型——int
		int i2=i1+b1;
		float f1=i1+b1;
		//~ float也可以 会转换为float类型
		short s1=123;
		double d1=s1;
		char c1='a';
		int i3=10;
		int i4=c1+i3;
		System.out.println(i4);
		short s2=10;
		//~ char c2=c1+s2; 不通过
		byte b2=10;
		//~ char c3=c1+b2;
		//~ short s3=b2+s2;
		//~ short s4=b1+b2;
	}
}

(2)强制类型转换

  • 强制类型转换需要使用 ()
  • 可能会导致精度损失

其他

class QiTa{
	public static void main(String[] args){
		long l=123123;//相当于int自动提升long
		//~ long l2=123121232123123123; 不通过 比int大
		//~ float f1=123.1;//失败
		float f2=123.1f;
		//~ byte b=12;
		byte b2=b+1; //失败
		//整型常量默认int
		//浮点型常量默认double
	}
} 

2.6 String

  • String属于引用数据类型
  • 声明String时,使用一对双引号:""
  • String可以与8中基本数据类型做运算,且只能是连接运算: +
  • 运算结果仍然是String

小练习

class lianxi{
	public static void main(String[] args){
		char c ='a';//97
		int num=10;
		String s="hello";
		System.out.println(c+num+s);//107hello
		System.out.println(c+s+num);//ahello10
		System.out.println(c+(num+s));//a10hello
		System.out.println((c+num)+s);//107hello
		System.out.println(s+num+c);//hello10a
	}
}
//String str1 = 123;//编译不通过
String str1 = 123 + "";
System.out.println(str1);//"123"

//int num1 = str1;
//int num1 = (int)str1;//"123"

int num1 = Integer.parseInt(str1);
System.out.println(num1);//123
System.out.println("*	*");
		System.out.println('*' + '\t' + '*');
		System.out.println('*' + "\t" + '*');
		System.out.println('*' + '\t' + "*");
		System.out.println('*' + ('\t' + "*"));

输出

*       *
93
*       *
51*
*       *

2.7 进制

  • 十进制 0-9
  • 二进制 0 1 以0b或0B开头
  • 八进制 0-7 以数字0开头表示。
  • 十六进制 0-9与a-f 以0x或0X开头表示。此处的A-F不区分大小写。
  • 源码、补码、反码以及相互转化:过
class BinaryTest {
	public static void main(String[] args) {
		int num1 = 0b110;
		int num2 = 110;
		int num3 = 0127;
		int num4 = 0x110A;
		System.out.println("num1 = " + num1);
		System.out.println("num2 = " + num2);
		System.out.println("num3 = " + num3);
		System.out.println("num4 = " + num4);
	}
}

输出

num1 = 6
num2 = 110
num3 = 87
num4 = 4362
int a=128;
byte b=a;
//b=-127
//原因:
//int : 128: 0000 0000 0000 0000 0000 0000 1000 0000
//byte:-127:                               1000 0000

2.8 运算符

算术运算符

图片.png

  • 除号
int num1 = 12;
int num2 = 5;
int result1 = num1 / num2;
System.out.println(result1);//2
int result2 = num1 / num2 * num2;
System.out.println(result2);//10
double result3 = num1 / num2;
System.out.println(result3);//2.0 运算的结果还是整形
double result4 = num1 / num2 + 0.0;//2.0 除法的结果是整形
double result5 = num1 / (num2 + 0.0);//2.4 加法的结果是double 在运算结果仍是double
double result6 = (double)num1 / num2;//2.4 强势类型转换
double result7 = (double)(num1 / num2);//2.0 只是对结果进行类型转换 转换前结果已经仍是整形
  • 取余
    • 结果的符号与被模数的符号相同
    • 开发中,经常使用%来判断能否被除尽的情况。(如果能除尽,结果是0)
int m1 = 12;
int n1 = 5;
System.out.println("m1 % n1 = " + m1 % n1); //2
int m2 = -12;
int n2 = 5;
System.out.println("m2 % n2 = " + m2 % n2); //-2
int m3 = 12;
int n3 = -5;
System.out.println("m3 % n3 = " + m3 % n3); //2
int m4 = -12;
int n4 = -5;
System.out.println("m4 % n4 = " + m4 % n4); //-2
  • 自增自减
    • ++a 先加法,再取值
    • a++ 先取值,再加一
    • a-- --a 同上
    • 自增运算不会改变变量的类型
    • 自增运算可以单独成语句运算
int a3 = 10;
++a3;//a3++;
int b=a3;//a3=11 b3=11

对于不同的变量,使用自增比使用a=a+1更便捷。

//注意点:
short s1 = 10;
//s1 = s1 + 1;//编译失败
//s1 = (short)(s1 + 1);//正确的
s1++;//自增1不会改变本身变量的数据类型
System.out.println(s1);
byte b=127;
b++;
//b=-128 原因上节有

练习:给出三位整数,输出它的个位、十位、百位。

int num = 187;
int bai = num / 100; //百位
int shi = num % 100 / 10;// 十位
int ge = num % 10; //个位
  • 赋值运算
    • 赋值符号: = int a=10
    • 连续赋值 a=b=c=10;
    • 变体 a+=1 类似于 a=a+1
    • -= *= /= %=
    • 当类型不同时,会自动进行强制类型转换。
short a=1;
// a=a+1;不通过
a+=1;//可以使用 不会给
```java
int i = 1;
i *= 0.1;
//相当于 i=i*0.1 结果是0
System.out.println(i);//0

int n = 10;
n += (n++) + (++n);
System.out.println(n)//32;

比较运算符

图片.png

  • 比较运算符的结果都是boolean型,也就是要么是true,要么是false。
  • ==!=:不仅可以使用在数值类型数据之间,还可以使用在其他引用类型变量之间。
  • 比较运算符“==”不能误写成“=”
int i = 10;
int j = 20;
System.out.println(i == j);//false
System.out.println(i = j);//20
//先进行 i=j 的赋值操作,之后显示i的值
boolean b1 = true;
boolean b2 = false;
System.out.println(b2 == b1);//false
System.out.println(b2 = b1);//true

逻辑运算符

图片.png

  • 逻辑运算符操作的都是布尔类型的! & 逻辑与 | 逻辑或 ! 逻辑非 && 短路与 || 短路或 ^ 逻辑异或
  • 逻辑与与短路与的区别: 当符号左边是false时,短路与直接判断为false,右边的不管了。 同理,逻辑或与短路或判断左边是否位true…… 以上都不会影响结果的真假。 开发最好用短路。

思考题

boolean x=true;
boolean y=false;
short z=42;
//if(y == true)
if((z++==42)&&(y=true))z++; //44
if((x=false) || (++z==45)) z++;
//(x=false)的值为 false
System. out.println("z="+z);
//z=46

位运算符

图片.png

  • 位运算运算对象是整形
  • 原理:
21   :     0000 0000 0000 0000 0000 0000 0001 0101
21<<2: 00|00 0000 0000 0000 0000 0000 0001 0101 00 = 84
相当于21*2*2=84
21   :     0000 0000 0000 0000 0000 0000 0001 0101
21>>2:     00|0000 0000 0000 0000 0000 0000 0001 01 = 5
相当于21*2*2=84
- `<<` 相当于乘以2
- `>>` 相当于/2
- 使用 `2<<3` 比使用 `2*8` 效率要高
  • 移位三者区别
    • << 移位时地位补0
    • >> 移位时高位原来符号位是1补1(考虑符号位),原来是0补0
    • >>> 移位时高位补0
  • 其他运算符
    • & 二进制位进行&运算,只有1&1时结果是1,否则是0;
    • | 二进制位进行 | 运算,只有0 | 0时结果是0,否则是1;
    • ^ 二进制位进行 ^ 运算,0;1^1=0 , 0^0=0 , 1^0=1 , 0^1=1
    • ~ 正数取反,各二进制码按补码各位取反。负数取反,各二进制码按补码各位取反。

小练习

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

//方式一:自动实现
String str1 = Integer.toBinaryString(60);
String str2 = Integer.toHexString(60);
//方式二:手动实现
int i1 = 60;
int i2 = i1&15;
String j = (i2 > 9)? (char)(i2-10 + 'A')+"" : i2+"";
int temp = i1 >>> 4;
i2 = temp & 15;
String k = (i2 > 9)? (char)(i2-10 + 'A')+"" : i2+"";
System.out.println(k+""+j);

小练习:交换变量值

int num1 = 10;
int num2 = 20;

如何交换两个变量num1num2的值?

  • 方法一:取中间变量
int temp = num1;
num1 = num2;
num2 = temp;
  • 方法二:
num1 = num1 + num2;
num2 = num1 - num2;
num1 = num1 - num2;
  • 方法三:
num1 = num1 ^ num2;
num2 = num1 ^ num2;
num1 = num1 ^ num2;

三元表达式(三目运算符)

  • 语法:(条件表达式)?表达式1:表达式2
  • 解释:先判断条件表达式的真假,如果为真执行表达式1,是假执行表达式2。
  • 注意,表达式1与2的值应一致。
  • 获取两个整数的较大值
int m = 12;
int n = 5;
int max = (m > n)? m : n;
System.out.println(max);
double num = (m > n)? 2 : 1.0;

实际上,三元运算符可以改写成if-else结构。(优先选择三元运算符)

if(m > n){
	System.out.println(m);
}else{
	System.out.println(n);
}
  • 获取三个数中的最大值
int n1 = 12;
int n2 = 30;
int n3 = -43;
int max1 = (n1 > n2)? n1 : n2;
int max2 = (max1 > n3)? max1 : n3;
System.out.println("三个数中的最大值为:" + max2);

运算符的优先级

最好不去考虑,编程时加上小括号就行()。 参考:blog.csdn.net/weixin_4966…

2.9 程序流程控制

三大流程结构

  • 顺序结构
  • 选择结构
  • 循环结构

选择结构-if

  • 第一种
if(条件表达式){
	执行表达式
}
  • 第二种:二选一
if(条件表达式){
	执行表达式1
}else{
	执行表达式2
}
  • 第三种:n选一
if(条件表达式){
	执行表达式1
}else if(条件表达式){
	执行表达式2
}else if(条件表达式){
	执行表达式3
}
...
else{
	执行表达式n
}
  • 注意,如果执行语句只有一行时,对应的大括号{}是可以省略的(但不建议省略)。

键盘输入变量

  1. 导包:import java.util.Scanner
  2. Scanner实例化(创建Scanner对象) Scanner scan=new Scanner(System.in)
  3. 使用对应的方法,格式:scan.next类型。没有char。
import java.util.Scanner;
class Shuru {
	public static void main(String[] args){
		Scanner scan=new Scanner(System.in);
		int num=scan.nextInt();
		System.out.println(num);
		System.out.println("请输入您的姓名:");
		String name=scan.next();
		System.out.println(name);
		System.out.println("请输入true或false");
		boolean boo=scan.nextBoolean();
		if(boo){
			System.out.println("Yes");
		}
		else{
			System.out.println("No");
		}
		System.out.println("请输入你的性别:(男/女)");
		String gender = scan.next();//"男"
		char genderChar = gender.charAt(0);//获取索引为0位置上的字符
		System.out.println(genderChar);
	}
}

注意,输入的值应与要求的类型匹配,不匹配会报异常。InputMismatchException

获取随机数

  • Math.random()返回一个随机的从0.0到1.0的double。
  • 例如,获取一个10-99的整形随机数
int value = (int)(Math.random() * 90 + 10);

字符串判断是否相等

  • zifuchuan.equals("要比较的字符串")
  • 此表达式返回boolean类型的值。

switch

  • 格式
switch(表达式){
case 常量1:
	执行语句1;
	//break;
case 常量2:
	执行语句2;
	//break;
...
default:
	执行语句n;
	//break;
}
  • 根据表达式的值依次匹配每个case中的常量。
  • case之后只能是常量,不能像if那样声明范围。
  • defalut是可选的,并且与其他的case位置随意(但默认放最后)。如果每个case的值与表达式的值不匹配,则执行defalut。
  • break是跳出每个case语句的入口。如果该case中没有break,继续执行下一个case。但是break是可选的。
  • switch结构中的表达式,原来默认为int类型。现在只能是如下的6种数据类型之一: byte 、short、char、int、枚举类型(JDK5.0新增)、String类型(JDK7.0新增) 前三种类型都能够被隐式地转换为int类型。然而long、float、double、不能够隐式地转换为int类型,因此它们不能被作为switch的表达式。(除非对其进行强制转换,但会产生误差。)

小练习

给出分数,80分以上输出“优秀”,否则输出“其他”。

int score=78;
switch(score/10){
	case 8:
	case 9:
	case 10:{
		System.out.println("合格");
		break;
	}
	defalut:
		System.out.println("其他");
		break;
}

循环结构

  • 四个组成部分
    1. 初始化部分
    2. 循环条件部分
    3. 循环体部分
    4. 迭代部分

for循环

for(1;2;4){
	3
}

图片.png

  • 注:break可以跳出循环。
  • for括号中三个表达式可以省略。规则如下:
    • 表达式1为空,也就是初始化为空。可以把初始化语句放到for前面。
    • 表达式2为空。默认为真,如果没有break跳出循环可能会死循环
    • 表达式3为空,迭代部分为空。迭代语句可以放到循环体里。

小练习:最大公约数与最小公倍数

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

另一种解法,供参考:blog.csdn.net/weixin_4966…

while循环

1
while(2){
	3
	4
}
  • 不要忘了迭代条件,否则可能进入死循环!
  • for与while可以互相转换

do-while

1
do{
3
4
}while(2);

小练习

键盘读入个数不确定的整数,并判断读入的正数和负数的个数,输入为0时结束程序。

Scanner scan = new Scanner(System.in);
int positiveNumber = 0;//记录正数的个数
int negativeNumber = 0;//记录负数的个数
for(;;){// 也可以写成while(true){
	int number = scan.nextInt();
	//判断number的正负情况
	if(number > 0){
		positiveNumber++;
	}else if(number < 0){
		negativeNumber++;
	}else{
		//一旦执行break,跳出循环
		break;
	}
}
System.out.println("输入的正数个数为:" + positiveNumber);
System.out.println("输入的负数个数为:" + negativeNumber);
  • 相比于while,do-while是先执行循环体,而后再判断。

嵌套循环

  • 大循环包小循环。
//输出
//*
//**
//***
//****
//*****
for(int i=1;i<=5;i++){
	for(int j=1;j<=i;j++)
		System.out.print('*');
	System.out.println();
}

小练习 九九乘法表

  • 要求输出以下内容
1 * 1 = 1
2 * 1 = 2 2 * 2 = 4
……
省略
……
9 * 1 = 9 ……………………… 9 * 9 = 81

代码:

for(int i=1;i<=9;i++){
	for(int j=1;j<=i;j++){
		System.out.print(i+" * "+j+" = "+(i*j)+" ");
	}
	System.out.println();
}

小练习 输出质数

  • 因数:整数a÷整数b(b≠0)正好能整除,我们就说b是a的因数。
  • 质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
  • 输出100以内所有质数
for(int i=2;i<=100;i++){
	boolean ZhiShu=true;
	for(int j=2;j<i;j++){
		if(i%j==0){
			//i能整除j,说明i不是质数
			ZhiShu=false;
			break;
		}
	}
	if(ZhiShu)
		System.out.print(i+" ");
}

进一步优化(原理:blog.csdn.net/sinat_26811…):

for(int i=2;i<=100;i++){
	boolean ZhiShu=true;
	for(int j=2;j*j<=i;j++){
	//原来的代码中第二层循环j从2遍历到i-1
	//注意,与上面的代码相比,判断条件改成了j*j<i
	//例如,84不是质数
	//84有因子4,那么84/4=21也是它的因数
	//如果a是b的一个因子,那么b/a也是b的一个因子
	//因此,假如有因子a、b,且a*b=num.其中必有一个大于sqrt(num) ,一个小于sqrt(num)
	//所以,只判断2~sqrt(num)是否为因子即可
	//还可以改成 for(int j=2;j<=Math.sqrt(i);j++){
		if(i%j==0){
			//i能整除j,说明i不是质数
			ZhiShu=false;
			break;
		}
	}
	if(ZhiShu)
		System.out.print(i+" ");
}

break和continue

  • break:
    • 用于switch-case与循环结构中
    • 结束当前循环
    • 关键字后面不能声明执行语句(有也执行不了)
  • continue:
    • 用于循环结构中
    • 结束(跳过)当次循环
    • 关键字后面不能声明执行语句(有也执行不了)
  • label
label:for(int i = 1;i <= 4;i++){
	for(int j = 1;j <= 10;j++){
		if(j % 4 == 0){
			//break;//默认跳出包裹此关键字最近的一层循环。
			//continue;
			//break label;//结束指定标识的一层循环结构
			continue label;//结束指定标识的一层循环结构当次循环
		}
		System.out.print(j);
	}
	System.out.println();
}

无限循环

  • for
for(;;){
	...
}
  • while
while(true){
	...
}
  • do-while
do{
	...
}while(true);

2.8 Eclipse的安装与使用

看视频吧

第三章 数组

3.1 基本概念

  • 数组是有序的元素序列。将有限个类型相同的变量集合起来顺序存储。
  • 数组是引用数据类型,其元素可以是基本数据类型也可以是引用数据类型。
  • 概念:
    • 数字名:数组的名字
    • 元素:存储的元素
    • 下标:元素的编号
    • 长度:元素的个数
  • 特点:数据有序排列,存储空间连续,确定长度后不能修改。
  • 分类
    • 维数:一维二维三维……
    • 元素类型:基本数据类型、引用数据类型

3.2 一维数组的使用

  • 声明与初始化
int a;//声明
a=100;//初始化
  • 一维数组的声明与初始化
    • 静态初始化:初始化与元素赋值操作同时进行
    • 动态初始化:初始化与元素赋值操作分开进行
int[] a;//声明
//静态初始化
a=new int[]{1001,1002,1003,1004};
//动态初始化
String[] names = new String[5];
//数组一旦初始化完成,其长度就确定了,不可更改
  • 调用数组中的元素
    • 数组中的元素编号都是从0开始的。0表示第一个元素。
names[0]="123";//第一个元素赋值
names[1]="1234";
names[2]="989";
names[3]="567";
names[4]="12355";//第五个元素
  • 获取数组的长度
name.length;
  • 遍历数组元素
for(int i=0;i<names.length;i++){
	System.out.println(names[i]);
}
  • 数组元素的默认初始化值
    • 数组元素是整型:0
    • 数组元素是浮点型:0.0
    • 数组元素是char型:0或'\u0000',而非'0'
    • 数组元素是boolean型:false
    • 数组元素是引用数据类型:null
int[] arr=new int[4];
for(int i=0;i<arr.length;i++){
	System.out.println(arr[i]);
}
  • 一维数组在内存

viewfile2.jpg

viewfile3.jpg

3.3 二维数组的使用

  • 二维数组的声明和初始化
//1.二维数组的声明和初始化
int[] arr = new int[]{1,2,3};//一维数组
//静态初始化
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}};
//动态初始化1
String[][] arr2 = new String[3][2];
//动态初始化2
String[][] arr3 = new String[3][];
//错误的情况 
//String[][] arr4 = new String[][4];
//String[4][3] arr5 = new String[][];
//int[][] arr6 = new int[4][3]{{1,2,3},{4,5},{6,7,8}};
//也是正确的写法:
int[] arr4[] = new int[][]{{1,2,3},{4,5,9,10},{6,7,8}};
int[] arr5[] = {{1,2,3},{4,5},{6,7,8}};
  • 调用数组的指定位置的元素
System.out.println(arr1[0][1]);//2
System.out.println(arr2[1][1]);//null
arr3[1] = new String[4];
System.out.println(arr3[1][0]);
  • 获取数组的长度
int[] arr4[] = new int[][]{{1,2,3},{4,5,9,10},{6,7,8}};
System.out.println(arr4.length);//3
System.out.println(arr4[0].length);//3
System.out.println(arr4[1].length);//4
  • 遍历二维数组
for(int i = 0;i < arr4.length;i++){
	
	for(int j = 0;j < arr4[i].length;j++){
		System.out.print(arr4[i][j] + "  ");
	}
	System.out.println();
}
  • 二维数组的使用

    • 规定:二维数组分为外层数组的元素,内层数组的元素
    • int[][] arr = new int[4][3];
    • 外层元素:arr[0],arr[1]等
    • 内层元素:arr[0][0],arr[1][2]等
  • 数组元素的默认初始化值

    • 针对于初始化方式一:比如:int[][] arr = new int[4][3]; 外层元素的初始化值为:地址值.内层元素的初始化值为:与一维数组初始化情况相同.
    • 针对于初始化方式二:比如:int[][] arr = new int[4][]; 外层元素的初始化值为:null。内层元素的初始化值为:不能调用,否则报错。
int[][] arr = new int[4][3];
System.out.println(arr[0]);//[I@15db9742 
System.out.println(arr[0][0]);//0
//System.out.println(arr);//[[I@6d06d69c

float[][] arr1 = new float[4][3];
System.out.println(arr1[0]);//地址值
System.out.println(arr1[0][0]);//0.0

String[][] arr2 = new String[4][2];
System.out.println(arr2[1]);//地址值
System.out.println(arr2[1][1]);//null

double[][] arr3 = new double[4][];
System.out.println(arr3[1]);//null
//System.out.println(arr3[1][0]);//报错
  • 二维数组的内存解析

viewfile.jpg

3.4 数据结构与算法

数据结构

  • 数据与数据之间的逻辑关系:集合、一对一、一对多、多对多
  • 数据的存储结构:
    • 线性表:顺序表(如:数组)、链表、栈、队列
    • 树形结构:二叉树
    • 图形结构:

算法

  • 排序算法
  • 搜索算法

练习

  • 声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是:
    • a) x[0] = y; no
    • b) y[0] = x; yes
    • c) y[0][0] = x; no
    • d) x[0][0] = y; no
    • e) y[0][0] = x[0]; yes
    • f) x = y; no

小练习:杨辉三角

int[][] yanghui = new int [10][];
for(int i=0;i<yanghui.length;i++) {
	yanghui[i]=new int[i+1];
	yanghui[i][0]=yanghui[i][i]=1;
	for(int j=1;j<yanghui[i].length-1;j++) {
		yanghui[i][j]=yanghui[i-1][j-1]+yanghui[i-1][j];
	}
}
for(int i=0;i<yanghui.length;i++) {
	for(int j=0;j<yanghui[i].length;j++) {
		System.out.print(yanghui[i][j]+" ");
	}
	System.out.println();
}

小练习:随机数的数组

  • 创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值。同时,要求 元素的值各不相同。
int[] arr = new int[6];
for (int i = 0; i < arr.length; i++) {// [0,1) [0,30) [1,31)
	arr[i] = (int) (Math.random() * 30) + 1;
	for (int j = 0; j < i; j++) {
		if (arr[i] == arr[j]) {
			i--;
			break;
		}
	}
}

小练习:回形数

Scanner s=new Scanner(System.in);
int n=s.nextInt();
int[][] a=new int[n][n];
int i=0,j=0,k=1;//k-方向 右下左上:1234
int c=1;
while(c<=n*n) {
	a[i][j]=c;
	System.out.println(i+","+j+":"+c);
	c++;
	//判断方向
	switch (k) {
	case 1:
		if(j+1>=a.length||a[i][j+1]!=0) {
			k=2;
			i++;
		}
		else {
			j++;
		}
		break;
	case 2:
		if(i+1>=a.length||a[i+1][j]!=0) {
			k=3;
			j--;
		}
		else {
			i++;
		}
		break;
	case 3:
		if(j-1<0||a[i][j-1]!=0) {
			k=4;
			i--;
		}
		else {
			j--;
		}
		break;
	case 4:
		if(i-1<0||a[i-1][j]!=0) {
			k=1;
			j++;
		}
		else {
			i--;
		}
		break;
	}
}

for(int ii=0;ii<n;ii++) {
	for(int jj=0;jj<n;jj++) {
		System.out.print(a[ii][jj]+"\t");
	}
	System.out.println();
}

小练习:求数组最大值最小值总和平均值

//遍历
for(int i = 0;i < arr.length;i++){
	System.out.print(arr[i] + "\t");
}
System.out.println();
//求数组元素的最大值
int maxValue = arr[0];
for(int i = 1;i < arr.length;i++){
	if(maxValue < arr[i]){
		maxValue = arr[i];
	}
}
System.out.println("最大值为:" + maxValue);

//求数组元素的最小值
int minValue = arr[0];
for(int i = 1;i < arr.length;i++){
	if(minValue > arr[i]){
		minValue = arr[i];
	}
}
System.out.println("最小值为:" + minValue);
//求数组元素的总和
int sum = 0;
for(int i = 0;i < arr.length;i++){
	sum += arr[i];
}
System.out.println("总和为:" + sum);
//求数组元素的平均数
int avgValue = sum / arr.length;
System.out.println("平均数为:" + avgValue);

3.5 数组的复制、反转、查找

array2=array1;

上面的语句不能实现数组的复制。两个数组索引都指向同一个数组。

String[] arr=new String[] {"1","2","3","4","%","6","7","8","9999"};
//复制
String[] arr1=new String[arr.length];
for(int i=0;i<arr1.length;i++) {
	arr1[i]=arr[i];
}

for(int i=0;i<arr1.length;i++) {
	System.out.print(arr1[i]+" ");
}
System.out.println();
  • Arrays 类的 copyOf() 方法与 copyOfRange() 方法都可实现对数组的复制。
    • copyOf() 方法是复制数组至指定长度
    • Arrays.copyOf(dataType[] srcArray,int length);
    • copyOfRange() 方法则将指定数组的指定长度复制到一个新数组中
    • Arrays.copyOfRange(dataType[] srcArray,int startIndex,int endIndex)
//反转
for(int i=0;i<arr.length/2;i++) {
	String temp=arr[i];
	arr[i]=arr[arr.length-i-1];
	arr[arr.length-i-1]=temp;
}

for(int i=0;i<arr.length;i++) {
	System.out.print(arr[i]+" ");
}
System.out.println();

//方案二
for(int i=0,j=arr.length-1;i<j;i++,j--) {
	String t=arr[i];
	arr[i]=arr[j];
	arr[j]=t;
}

for(int i=0;i<arr.length;i++) {
	System.out.print(arr[i]+" ");
}
System.out.println();
//查找
//线性查找
String findS="9999";
boolean findb=false;
for(int i=0;i<arr.length;i++) {
	if(findS.equals(arr[i])){
		System.out.println("已找到。位置"+i);
		findb=true;
		break;
	}
}
if(!findb) {
	System.out.println("没找到!");
}
//二分查找
//前提,数组必须按顺序排列
int[] a=new int[] {1,2,3,4,5,6,7,8,9,12,13,14,15,46,57,134,456,676,1241,57387};
int findNum=134;

boolean isFind=false;
int l=0,r=a.length-1;//左、右
while(l<=r) {
	int mid=(r+l)/2;
	if(a[mid]>findNum) {
		r=mid-1;
	}
	else if(a[mid]<findNum) {
		l=mid+1;
	}
	else {
		System.out.println("找到了!位于"+mid);
		isFind=true;
		break;
	}
}
if(!isFind) {
	System.out.println("没有找到!");
}

3.6 排序算法

算法的5大特征

特征描述
输入(Input)有0个或多个输入数据,这些输入必须有清楚的描述和定义
输出(Output)至少有1个或多个输出结果,不可以没有输出结果
有穷性(有限性,Finiteness)算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成
确定性(明确性,Definiteness)算法中的每一步都有确定的含义,不会出现二义性
可行性(有效性,Effectiveness)算法的每一步都是清楚且可行的,能让用户用纸笔计算而求出答案

排序算法分类

  • 排序算法分类:内部排序外部排序
  • 内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
  • 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。

十大内部排序算法

  • 选择排序
    • 直接选择排序、堆排序
  • 交换排序
    • 冒泡排序、快速排序
  • 插入排序
    • 直接插入排序、折半插入排序、Shell排序
  • 归并排序
  • 桶式排序
  • 基数排序

冒泡排序

//冒泡排序
for(int i=0;i<a.length;i++) {
	for(int j=0;j<a.length-1-i;j++){
		if(a[j]>a[j+1]) {
			int t=a[j];
			a[j]=a[j+1];
			a[j+1]=t;
		}
	}
}

快速排序

  • 快排的性能在所有排序算法里面是最好的,数据规模越大快速排序的性能越优。
  • 原理讲解:zhuanlan.zhihu.com/p/93129029
  • 后期补上

3.7 数组的工具

  • boolean equals(int[] a,int[] b):判断两个数组是否相等。
int[] arr1 = new int[]{1,2,3,4};
int[] arr2 = new int[]{1,3,2,4};
boolean isEquals = Arrays.equals(arr1, arr2);
System.out.println(isEquals);
  • String toString(int[] a):输出数组信息。
System.out.println(Arrays.toString(arr1));
  • void fill(int[] a,int val):将指定值填充到数组之中。
Arrays.fill(arr1,10);
System.out.println(Arrays.toString(arr1));
  • void sort(int[] a):对数组进行排序。
Arrays.sort(arr2);
System.out.println(Arrays.toString(arr2));
  • int binarySearch(int[] a,int key):数组二分查找。
int[] arr3 = new int[]{-98,-34,2,34,54,66,79,105,210,333};
int index = Arrays.binarySearch(arr3, 210);
if(index >= 0){
	System.out.println(index);
}else{
	System.out.println("未找到");
}

3.8 数组中的常见异常

  1. 数组角标越界的异常:ArrayIndexOutOfBoundsExcetion
int[] arr = new int[]{1,2,3,4,5};
//for(int i = 0;i <= arr.length;i++){
//		System.out.println(arr[i]);
//}
//System.out.println(arr[-2]);
  1. 空指针异常:NullPointerException
//情况一:
int[] arr1 = new int[]{1,2,3};
arr1 = null;
System.out.println(arr1[0]);
//情况二:
int[][] arr2 = new int[4][];
System.out.println(arr2[0][0]);
//情况三:
String[] arr3 = new String[]{"AA","BB","CC"};
arr3[0] = null;
System.out.println(arr3[0].toString());
  • 一旦程序出现异常,如果没有解决,程序终止运行。

第四章 面向对象 上

  • 学习面向对象内容的三条主线
    1. Java类及类的成员:属性、方法、构造器;代码块、内部类
    2. 面向对象的三大特征:封装性、继承性、多态性、(抽象性)
    3. 其它关键字:this、super、static、final、abstract、interface、package、import等

4.1 基本概念

面向对象与面向过程

概念自己搜。

类与对象

  • 类:抽象
  • 对象:类的个体

属性与方法

  • 属性:类的属性。类所具有的特性。
  • 方法:类的函数。类有什么功能,能干什么。

4.2 对象的创建与使用

  1. 创建类,设计类的成员
  2. 创建类的对象
  3. 通过“对象.属性”或“对象.方法”调用对象的结构
  • 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)。也就是说,如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
//1.创建类,设计类的成员
class Person{
	
	//属性
	String name;
	int age = 1;
	boolean isMale;
	
	//方法
	public void eat(){
		System.out.println("人可以吃饭");
	}
	public void sleep(){
		System.out.println("人可以睡觉");
	}
	public void talk(String language){
		System.out.println("人可以说话,使用的是:" + language);
	}
	
}

public class PersonTest {
	public static void main(String[] args) {
		//2. 创建Person类的对象
		Person p1 = new Person();
		//Scanner scanner = new Scanner(System.in);
		
		//调用对象的结构:属性、方法
		//调用属性:“对象.属性”
		p1.name = "Tom";
		p1.isMale = true;
		System.out.println(p1.name);
		
		//调用方法:“对象.方法”
		p1.eat();
		p1.sleep();
		p1.talk("Chinese");
		
		//*******************************
		Person p2 = new Person();
		System.out.println(p2.name);//null
		System.out.println(p2.isMale);
		//*******************************
		//将p1变量保存的对象地址值赋给p3,导致p1和p3指向了堆空间中的同一个对象实体。
		Person p3 = p1;
		System.out.println(p3.name);//Tom
		
		p3.age = 10;
		System.out.println(p1.age);//10
	}
}

内存解析

图片.png

  • 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
  • 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
  • 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

属性与局部变量

  • 属性(类的成员变量)与局部变量
class User{
	//属性(或成员变量)
	String name;
	public int age;
	boolean isMale;
	public void talk(String language){//language:形参,也是局部变量
		System.out.println("我们使用" + language + "进行交流");
		
	}
	public void eat(){
		String food = "烙饼";//局部变量
		System.out.println("北方人喜欢吃:" + food);
	}
}
  • 相同点
    • 定义格式:数据类型 变量名 = 变量值
    • 先声明,后使用
    • 变量都有对应的作用域
  • 不同点
    • 在类中声明的位置不同:
    • 属性:直接定义在类的{}内。
    • 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
    • 关于权限修饰符的不同
    • 属性:可以在声明属性时,指明其权限,使用权限修饰符。常用的权限修饰符:private、public、缺省、protected --->封装性
    • 局部变量:不可以使用权限修饰符。
    • 默认初始化值
    • 属性:都有默认初始化值
      • 整形:0 浮点型:0.0 字符型:0 布尔型:false 引用数据类型:null
    • 局部变量:无初始化值
    • 内存状态
    • 属性:堆
    • 局部变量:栈

4.3 方法的定义与调用

方法的声明

  • 语法:
权限修饰符 返回值类型 方法名(形参列表){
	方法体
}
  • 权限修饰符
    • privatepublic、缺省、protected
  • 返回值类型
    • 有返回值:如果方法有返回值,必须在方法声明时指定返回值的类型。同时方法中需要使用return返回对应类型的值(常量或变量)。
    • 无返回值:使用void表示,一般不需要return返回类型。也可以加上,但return后不能加上值,表示结束此方法。
  • 方法名:起的名字要符合要求。
  • 形参列表
    • 方法可以声明0个,1个或多个形参
    • 格式:数据类型1 形参1,数据类型2 形参2,...
  • return
    • 适用范围:方法体中。
    • 作用:结束方法、返回数据(如果方法是有返回值的话)
    • 注意:return 后不可跟执行语句。

方法的使用

  • 方法的使用中可以调用当前类的属性和方法(包括可以调用该方法自己,即归方法)。
  • 方法里不能定义方法。

匿名对象

  • 理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象
  • 特征:匿名对象只能调用一次
public class NiMing {
	public static void main(String[] args) {
		new NiMingClass().a=100;
		new NiMingClass().fangfa();
		useClass u=new useClass();
		u.use(new NiMingClass());
	}
}
class NiMingClass{
	int a;
	public void fangfa() {
		System.out.println("Test");
	}
}
class useClass{
	public void use(NiMingClass n) {
		n.fangfa();
		n.a=123;
		System.out.println(n.a);
	}
}

小练习:自定义数组工具类

public class ArrayUtil {
	//求最大值
	public int getMax(int[] arr) {
		int maxValue = arr[0];
		for(int i = 1;i < arr.length;i++){
			if(maxValue < arr[i]){
				maxValue = arr[i];
			}
		}
		return maxValue;
	}
	//求最小值
	public int getMin(int[] arr) {
		int minValue = arr[0];
		for(int i = 1;i < arr.length;i++){
			if(minValue > arr[i]){
				minValue = arr[i];
			}
		}
		return minValue;
	}
	//求和
	public int getSum(int[] arr) {
		int sum = 0;
		for(int i = 0;i < arr.length;i++){
			sum += arr[i];
		}
		return sum;
	}
	//求平均值
	public int getAvg(int[] arr) {
		int avgValue = getSum(arr) / arr.length;
		return avgValue;
	}
	//反转
	public void reverse(int[] arr) {
		for(int i=0;i<arr.length/2;i++) {
			int temp=arr[i];
			arr[i]=arr[arr.length-i-1];
			arr[arr.length-i-1]=temp;
		}
	}
	//复制
	public int[] copy(int[] arr) {
		int[] arr1=new int[arr.length];
		for(int i=0;i<arr1.length;i++) {
			arr1[i]=arr[i];
		}
		return arr1;

	}
	//排序
	public void sort(int[] a) {
		for(int i=0;i<a.length;i++) {
			for(int j=0;j<a.length-1-i;j++){
				if(a[j]>a[j+1]) {
					int t=a[j];
					a[j]=a[j+1];
					a[j+1]=t;
				}
			}
		}
	}
	//遍历数组
	public void print(int[] arr) {
		for(int i=0;i<arr.length;i++){
			System.out.print(arr[i]+" ");
		}
		System.out.println();
	}
	
	public int getIndex(int[] a,int findNum) {
		int l=0,r=a.length-1;//左、右
		while(l<=r) {
			int mid=(r+l)/2;
			if(a[mid]>findNum) {
				r=mid-1;
			}
			else if(a[mid]<findNum) {
				l=mid+1;
			}
			else {
				return mid;
			}
		}
		return -1;
	}
}

测试

public class ArrayUtilTest {
	public static void main(String[] args) {
		int[] a = new int[] {1,2,3,4,54,75,2,68,23,48,2,35,765735,74,23,66,4532,76};
		ArrayUtil au=new ArrayUtil();
		au.print(a);
		System.out.println(au.getMax(a));
		System.out.println(au.getMin(a));
		System.out.println(au.getAvg(a));
		System.out.println(au.getSum(a));
		au.reverse(a);
		au.print(a);
		au.sort(a);;
		au.print(a);
		System.out.println(au.getIndex(a, 4532));
		System.out.println(au.getIndex(a, 4531));
		int[] b = au.copy(a);
		au.print(b);
	}
}

输出

1 2 3 4 54 75 2 68 23 48 2 35 765735 74 23 66 4532 76 
765735
1
42823
770823
76 4532 66 23 74 765735 35 2 48 23 68 2 75 54 4 3 2 1 
1 2 2 2 3 4 23 23 35 48 54 66 68 74 75 76 4532 765735 
16
-1
1 2 2 2 3 4 23 23 35 48 54 66 68 74 75 76 4532 765735 

小练习 类的内存结构

图片.png

4.4 方法的重载

重载的使用

  • 在同一个类中,允许存在一个以上同名方法,其中每个方法形参列表不同。
  • 特点:同一个类,同一个名,参数列表不同(参数个数不同,参数类型不同)
class cz {
	public int aa(int a,int b) {
		return a+b;
	}
	public double aa(double a,double b) {
		return a+b;
	}
	public double aa(double a,double b,double c) {
		return a+b+c;
	}
	public int aa(String a,String b) {
		System.out.println(a+b);
		return 1;
	}
//	public int aa(int c,int d) {
//		return c+d;
//	}
//	不通过,参数列表相同
}
public class ChongZai{
	public static void main(String[] args) {
		cz cz1=new cz();
		System.out.println(cz1.aa(1,2));
		System.out.println(cz1.aa(1.11,2.99));
		System.out.println(cz1.aa(1.11,2.99,3.1234));
		System.out.println(cz1.aa("123","456"));
	}
}

可变个数形参的方法

  • 格式 数据类型 ... 变量名
  • 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个,。。。
  • 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
  • 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
  • 可变个数形参在方法的形参中,必须声明在末尾
  • 可变个数形参在方法的形参中,最多只能声明一个可变形参。
class t {
	public void show(String s) {
		System.out.println("FangFa1");
	}
	public void show(String ... s) {
		System.out.println("Chanede Array.");
		//s 相当于是个数组
		for(int i=0;i<s.length;i++) {
			System.out.println(s[i]);
		}
	}
//	public void show(String[] a) {
//		System.out.println("Test");
//	}
//	重复,编译不通过
//	public void show(String ... s,int a) {
//		不通过,可变形参只能放到形参列表最后
//	}
	public void show(int a,String ... s) {
		System.out.println("Chanede Array.222");
	}
}
public class KeBianGeShuXingCan{
	public static void main(String[] args) {
		t tttt=new t();
		tttt.show("123");
		tttt.show("123","1279731","271974927409","13");
		tttt.show(123,"123","1279731","271974927409","13");
	}
}

4.5 值传递机制

赋值

  • 基本数据类型赋值
int m=10;
int n=m;//m=10 n=10
m=20;
//m=20 n=10
  • 引用数据类型赋值
AClass a=new AClass();
a.id=100;
AClass b=a;
//赋值之后,b中存的是a指向对象的地址值,a与b指向堆中同一个对象实体。
b.id=200;
Sysout(a.id);//200

方法形参的传递机制:值传递

方法形参的传递机制:值传递

  1. 形参:定义方法时,小括号内的参数。
  2. 实参:方法调用时,实际传递给形参的数据。
public class ValueTransferTest1 {
	public static void main(String[] args) {
		int m = 10;
		int n = 20;
		System.out.println("m = " + m + ", n = " + n);
		//输出 10 20
		
		//交换两个变量的值的操作
		ValueTransferTest1 test = new ValueTransferTest1();
		test.swap(m, n);
		System.out.println("m = " + m + ", n = " + n);
		//输出 10 20
	}
	public void swap(int m,int n){
		int temp = m ;
		m = n;
		n = temp;
		System.out.println("m = " + m + ", n = " + n);
		//输出的是被交换的值
	}
}
  • 如果参数是基本数据类型,实参传递给形参的值是数据。
  • 如果参数是引用数据类型,实参传递的是实参所引用对象的地址值。(例如“小练习:自定义数组工具类”中的反转数组函数。)
int[] arr=new int[]{1,2,3,4,5,6,7};
System.out.println(arr);//输出的是地址值
char[] arr1=new char[10]{'1','2','3','4','5','6','7'};
System.out.println(arr1);//输出1234567
  • 原因:println()重载

递归方法的使用

  • 递归:自己调用自己
public class DiGui {
	//计算1-n之间所有整数的和
	public int getSum(int n) {
		//getSum(n)=getSum(n-1)+n
		if(n==1) {
			return 1;
		}
		else {
			return n+getSum(n-1);
		}
	}
	//计算1-n之间所有整数的乘积
	public long getCheng(long n) {
		//getSum(n)=getSum(n-1)+n
		if(n==1) {
			return 1;
		}
		else {
			return n*getCheng(n-1);
		}
	}
	//已知有一个数列 f(0)=1 f(1)=4 f(n+2)=2*f(n+1)+f(n)
	public int getf(int n) {
		if(n==0) {
			return 1;
		}
		else if(n==1) {
			return 4;
		}
		else {
			return 2*getf(n-1)+getf(n-2);
		}
	}
	public static void main(String[] args) {
		System.out.println(new DiGui().getSum(100));
		System.out.println(new DiGui().getCheng(10l));
		System.out.println(new DiGui().getf(0));
		System.out.println(new DiGui().getf(1));
		System.out.println(new DiGui().getf(2));
		System.out.println(new DiGui().getf(3));
		System.out.println(new DiGui().getf(4));
	}
}
  • 汉诺塔问题 后续补充
  • 斐波那契数列 后续补充
  • 上台阶问题 后续补充
  • 快排 后续补充

4.6 面向对象的特征之一:封装与隐藏

  • 高内聚、低耦合:内部细节不暴露,外部公开简单接口

封装

  • 封装:有些类的属性不需要用户去深究,这时候为了把这些属性封装起来,可以把它进行私有化。
  • 封装性的体现需要权限修饰符的配合
public class FengZhuang {
	public static void main(String[] args) {
		Dog a=new Dog();
		// a.age=13; 会报错
		a.setName("XiaoHei");
		a.setAge(10);
		a.show();
	}
}
class Dog{
	private String name=new String();
	private int age;
	public void setName(String a) {
		name=a;
	}
	public void setAge(int a) {
		age=a;
	}
	public void show() {
		System.out.println(name+" : "+age);
	}
}
  • Java中的四种权限:private、缺省、publicprotected
修饰符类内部同一个包不同包的子类同一个工程
privateyes
缺省yesyes
protectedyesyesyes
publicyesyesyesyes
  • 这四种权限可以修饰类和类的内部结构:属性、方法、构造器、内部类
    • 类只能使用public或缺省

构造器

  • 作用:创建对象、初始化对象信息
  • 如果没有显示地定义构造器,系统会默认提供一个没有参数的构造器
  • 语法:
权限修饰符 类名(参数列表){
}
  • 一个类中,一定有构造器。如果有自己定义的构造器,使用构造器时必须与自己定义的构造器一致。
public class GouZaoQi {
	public static void main(String[] args) {
		Person a = new Person();
		a.show();
		Person b=new Person("XiaoLi",19);
		b.show();
	}
}
class Person{
	private int age;
	private String name=new String();
	public Person() {
		age =0;
		name="none";
	}
	public Person(String n,int a) {
		age=a;
		name = n;
	}
	public void show() {
		System.out.println(name+" : "+age);
	}
}
//none : 10
//XiaoLi : 19

4.7 JavaBean

  • JavaBean,是指符合如下标准的Java类:
    • 类是公共的
    • 有一个无参的公共的构造器
    • 有属性,且有对应的get、set方法

4.8 UML类图

4.9 this

this的使用

class Number{
	private void int number;
	public setNum(int number) {
		number=number;
	}
}
  • 以上代码会报错。原因是方法setNum里的参数number名字与该类的属性number重名
  • 修改后如下:
class Number{
	private int number;
	public void setNum(int number) {
		this.number=number;
	}
}
  • this可以修饰:属性、方法、构造器
  • this可以理解为当前对象。
  • 一般不使用this,但是如果方法的形参与类的属性同名时,需要使用this

this调用构造器

  • 调用构造器:this();
  • 构造器中不能自己调自己,也不能互相调。(每个构造器只能调用一次this(。。)。如果一个类中有n个构造器,则最多有n-1个构造器能使用this())
  • 使用这种调用必须写在该构造器中的首行。
class Number{
	int a,b,c,d;
	public Number() {
		this.a=this.b=this.c=this.d=1;
	}
	public Number(int a) {
		this();
		this.a=a;
	}
	public Number(String s) {
		this();
		System.out.println(s);
	}
}

小练习 银行

bank_234

4.10 关键字package import的使用

package

  • 为了更好的实现项目中类的管理,提供包的概念。
  • 使用package声明类或接口所属的包,声明写在源文件的首行
  • 包命名时遵循规范(xxxyyyzzz)
  • .表示一层文件目录
  • 同一个包下不能命名同名的接口或类。不同的包下可以。

MVC设计模式

MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。

  • 模型- model 主要处理数据
    • 数据对象封装 model.bean/domain
    • 数据库操作类 model.dao
    • 数据库 model.db
  • 视图层-view 显示数据
    • 相关工具类 view.utils
    • 自定义view view.ui
  • 控制层 controller 处理业务逻辑
    • 应用界面相关 controller.activity
    • 存放fragment controller.fragment
    • 显示列表的适配器 controller.adapter
    • 服务相关的 controller.service
    • 抽取的基类 controller.base

import

  • import:在源文件中显式地使用import导入指定包下的类或接口
  • 声明在package与类的声明之间
  • 可以使用xxx.*的方式表示导入xxx包下的所有结构。如果使用子包xxx.yyy下的结构,仍需写xxx.yyy.*
  • 如果使用的类或接口是java.lang下定义的,不用导入,Java默认的。
  • 如果使用的类或接口是本包下定义的,不用import。
  • 如果需要导入不同包下同名的类或接口,需要在使用时写上包的全称。
  • import static .... 调用包里静态结构

第五章 面向对象 中

5.1 Eclipse 快捷键 与 Debug

  • blog.csdn.net/weixin_4091…
  • 修改快捷键:Preferences->搜索“keys”->修改
  • Eclipse中的快捷键:
    • 1.补全代码的声明:alt + /
    • 2.快速修复: ctrl + 1
    • 3.批量导包:ctrl + shift + o
    • 4.使用单行注释:ctrl + /
    • 5.使用多行注释: ctrl + shift + /
    • 6.取消多行注释:ctrl + shift + \
    • 7.复制指定行的代码:ctrl + alt + down 或 ctrl + alt + up
    • 8.删除指定行的代码:ctrl + d
    • 9.上下移动代码:alt + up 或 alt + down
    • 10.切换到下一行代码空位:shift + enter
    • 11.切换到上一行代码空位:ctrl + shift + enter
    • 12.如何查看源码:ctrl + 选中指定的结构 或 ctrl + shift + t
    • 13.退回到前一个编辑的页面:alt + left
    • 14.进入到下一个编辑的页面(针对于上面那条来说的):alt + right
    • 15.光标选中指定的类,查看继承树结构:ctrl + t
    • 16.复制代码: ctrl + c
    • 17.撤销: ctrl + z
    • 18.反撤销: ctrl + y
    • 19.剪切:ctrl + x
    • 20.粘贴:ctrl + v
    • 21.保存: ctrl + s
    • 22.全选:ctrl + a
    • 23.格式化代码: ctrl + shift + f
    • 24.选中数行,整体往后移动:tab
    • 25.选中数行,整体往前移动:shift + tab
    • 26.在当前类中,显示类结构,并支持搜索指定的方法、属性等:ctrl + o
    • 27.批量修改指定的变量名、方法名、类名等:alt + shift + r
    • 28.选中的结构的大小写的切换:变成大写: ctrl + shift + x
    • 29.选中的结构的大小写的切换:变成小写:ctrl + shift + y
    • 30.调出生成getter/setter/构造器等结构: alt + shift + s
    • 31.显示当前选择资源(工程 or 文件)的属性:alt + enter
    • 32.快速查找:参照选中的Word快速定位到下一个 :ctrl + k
    • 33.关闭当前窗口:ctrl + w
    • 34.关闭所有的窗口:ctrl + shift + w
    • 35.查看指定的结构使用过的地方:ctrl + alt + g
    • 36.查找与替换:ctrl + f
    • 37.最大化当前的View:ctrl + m
    • 38.直接定位到当前行的首位:home
    • 39.直接定位到当前行的末位:end
  • Debug:

图片.png

图片.png

5.2 继承性

  • 面向对象特征之二——继承性
  • 好处:减少代码冗余,便于功能扩展,为多态性提供前提

格式

  • class A extends B{}
    • A:子类、派生类、subclass
    • B:父类、基类、superclass
  • 一旦子类A继承父类B之后,子类可以获取到父类的属性、方法。但是private的仍然不能直接调用。
  • 子类继承父类以后,还可以定义自己的属性和方法,实现功能的拓展

规定

  • 一个类可以被多个类继承
  • 一个子类只能继承一个父类:单继承
  • 类是可以多层继承的。
    • 直接继承的父类——直接父类
    • 简介继承的父类——简介父类

Object

  • 如果没有显示声明一个类的父类,则此类默认继承与java.lang.Object
  • 所有类都直接或简介继承Object类
  • 可以调用Object类的功能。

5.3 方法的重写

  • 在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
  • 规定:
方法的声明:
权限修饰符  返回值类型  方法名(形参列表) throws 异常的类型{
		//方法体
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
  • 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
  • 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
    • 特殊情况:子类不能重写父类中声明为private权限的方法
  • 返回值类型:
    • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
    • 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
    • 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
  • 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
  • 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。static不可被重写。

5.4 super关键字

  • super理解为:父类的
  • super可以用来调用:属性、方法、构造器
  • super的使用:调用属性和方法
    • 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
    • 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
    • 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
  • super调用构造器
    • 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
    • "super(形参列表)"的使用,必须声明在子类构造器的首行!
    • 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
    • 在构造器的首行,没有显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
    • 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器

5.5 多态性

多态性的使用

  • 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
  • 多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
    • 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
    • 总结:编译,看左边;运行,看右边。
    • 不能调用子类特有的方法!
  • 多态性的使用前提: ① 类的继承关系 ② 方法的重写
  • 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
  • 多态性是运行时行为。
package com.learn.duotai;
import java.util.Random;
//面试题:多态是编译时行为还是运行时行为?
//证明如下:
class Animal  {
	protected void eat() {
		System.out.println("animal eat food");
	}
}
class Cat  extends Animal  {
	protected void eat() {
		System.out.println("cat eat fish");
	}
}
class Dog  extends Animal  {
	public void eat() {
		System.out.println("Dog eat bone");
	}
}
class Sheep  extends Animal  {
	public void eat() {
		System.out.println("Sheep eat grass");
	}
}
public class InterviewTest {
	public static Animal  getInstance(int key) {
		switch (key) {
		case 0:
			return new Cat ();
		case 1:
			return new Dog ();
		default:
			return new Sheep ();
		}
	}
	public static void main(String[] args) {
		int key = new Random().nextInt(3);
		//选取随机数,key
		System.out.println(key);
		Animal animal = getInstance(key);
		//animal 的多态性
		animal.eat();
		//每次运行时,Key的取值不同,调用对应的方案不同
	}
}

重载与重写

  • 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
  • 多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
  • 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
  • 如何才能调用子类特有的属性和方法?:向下转型:使用强制类型转换符。
Man m1 = (Man)p2;
m1.earnMoney();
m1.isSmoking = true;
  • 使用强转时,可能出现ClassCastException的异常。
//p2是Man类
Woman w1 = (Woman)p2;
w1.goShopping();

instanceof

  • a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
  • 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
  • 如果 a instanceof A返回true,则 a instanceof B也返回true.其中,类B是类A的父类。

练习

问题一:编译时通过,运行时不通过 举例一:

Person p3 = new Woman();
Man m3 = (Man)p3;

举例二:

Person p4 = new Person();
Man m4 = (Man)p4;

问题二:编译通过,运行时也通过

Object obj = new Woman();
Person p = (Person)obj;

问题三:编译不通过

Man m5 = new Woman();
String str = new Date();

练习

class Base {
	int count = 10;
	public void display() {
		System.out.println(this.count);
	}
}

class Sub extends Base {
	int count = 20;
	public void display() {
		System.out.println(this.count);
	}
}
public class FieldMethodTest {
	public static void main(String[] args) {
		Sub s = new Sub();
		System.out.println(s.count);//20
		s.display();//20
		
		Base b = s;//多态性
		//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
		System.out.println(b == s);//true
		System.out.println(b.count);//10
		b.display();//20
	}
}
  1. 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
  2. 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边

练习

class Base {
	public void add(int a, int... arr) {
		System.out.println("base");
	}
}
class Sub extends Base {
	public void add(int a, int... arr) {
		System.out.println("sub_1");
	}
}
public class InterviewTest1 {
	public static void main(String[] args) {
		Base base = new Sub();
		base.add(1, 2, 3);
		//sub_1
		Sub s = (Sub)base;
		s.add(1,2,3);
		//sub_1
	}
}
package com.learn.duotaiex;
class Base {
	public void add(int a, int... arr) {
		System.out.println("base");
	}
}
class Sub extends Base {
	public void add(int a, int b, int c) {
		System.out.println("sub_2");
	}
	public void add(int a, int... arr) {
		System.out.println("sub_1");
	}
}
public class InterviewTest1 {
	public static void main(String[] args) {
		Base base = new Sub();
		base.add(1, 2, 3);
		// sub_1
		//这里是sub_1的原因是因为
		//父类与子类都有方法:void add(int a, int... arr)
		//多态时父类的方法被子类重写
		//调用子类对应重写的方法
		//尽管(1, 2, 3)与(1, 2, 3)匹配
		//但是它不是父类重写的方法
		Sub s = (Sub) base;
		s.add(1, 2, 3);
		// sub_2
		//此时直接调用子类的方法
		//add(int a, int b, int c)
	}
}

5.6 Object

Object

  • Object类是所有Java类的根父类
  • 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
  • Object类中的功能(属性、方法)就具有通用性。
    • 属性:无
    • 方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()、wait() 、 notify()、notifyAll()
  • Object类只声明了一个空参的构造器

equals()

  • equals()是一个方法,而非运算符
  • 只能适用于引用数据类型
  • Object类中equals()的定义:
public boolean equals(Object obj) {
    return (this == obj);
}
- 说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体 像StringDate、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
  • 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写.
    • 重写的原则:比较两个对象的实体内容是否相同.

对比==

== :运算符

  • 可以使用在基本数据类型变量和引用数据类型变量中
  • 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
  • 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
  • 补充: == 符号使用时,必须保证符号左右两边的变量类型一致。(这里主要是如果两个变量是引用数据类型,如果不是同一类(或者无父子关系)那么编译就会报错)。

equals()的重写

  • 手动实现equals()的重写
public boolean equals(Object obj) {	
	if (this == obj) {
        return true;
    }
	if(obj instanceof Customer){
		Customer cust = (Customer)obj;
		//思考:为什么在这里进行强制类型转换?
		//因为参数类型是Object类的
		//要比较内容是否相同,需要使用内容属性
		//将Object转换成目标类,就可以比较了
		return this.age == cust.age && this.name.equals(cust.name);
	}
	return false;
}
  • 实际直接使用Eclipse中就可以自动重写。

练习

/*
 * 练习:
 * 1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,
 * 系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
 * 2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
 * 这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边
 */
class Base {
	int count = 10;
	public void display() {
		System.out.println(this.count);
	}
}
class Sub extends Base {
	int count = 20;

	public void display() {
		System.out.println(this.count);
	}
}
public class FieldMethodTest {
	public static void main(String[] args) {
		Sub s = new Sub();
		System.out.println(s.count);//20
		s.display();//20
		Base b = s;//多态性
		//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
		System.out.println(b == s);//true
		System.out.println(b.count);//10
		b.display();//20
	}
}

toString()

  • 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
System.out.println(cust1.toString());
//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21]
System.out.println(cust1);
//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21]
  • Object类中toString()的定义:
public String toString() {
   return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息
  • 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
  • Eclipse也可以自动生成。

5.7 包装类

Java中的JUnit单元测试

  1. 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
  2. 创建Java类,进行单元测试。
    • 此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
  3. 此类中声明单元测试方法。此时的单元测试方法:方法的权限是public,没有返回值,没有形参
  4. 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
  5. 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
  6. 写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
  • 说明:
    1. 如果执行结果没有任何异常:绿条
    2. 如果执行结果出现异常:红条
  • 在Eclipse中,只需要输入@Test,之后由程序改错,就可以自动导包了。
public class JUnitTest {
	//可以自定义属性
	int num = 10;
	
	@Test
	public void testEquals(){
		String s1 = "MM";
		String s2 = "MM";
		System.out.println(s1.equals(s2));
		//ClassCastException的异常
//		Object obj = new String("GG");
//		Date date = (Date)obj;

		//其余与正常的方法没有什么区别
		System.out.println(num);
		show();
	}
	public void show(){
		num = 20;
		System.out.println("show()....");
	}
	
	@Test
	public void testToString(){
		String s2 = "MM";
		System.out.println(s2.toString());
	}
}
  • java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
  • Have problem. Passed
package com.atguigu.java2;

import org.junit.Test;

/*
 * 包装类的使用:
 * 1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
 * 
 * 2.掌握的:基本数据类型、包装类、String三者之间的相互转换
 * 
 * 
 * 
 */
public class WrapperTest {
	
	//String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
	@Test
	public void test5(){
		String str1 = "123";
		//错误的情况:
//		int num1 = (int)str1;
//		Integer in1 = (Integer)str1;
		//可能会报NumberFormatException
		int num2 = Integer.parseInt(str1);
		System.out.println(num2 + 1);
		
		String str2 = "true1";
		boolean b1 = Boolean.parseBoolean(str2);
		System.out.println(b1);
	}
	
	//基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
	@Test
	public void test4(){
		
		int num1 = 10;
		//方式1:连接运算
		String str1 = num1 + "";
		//方式2:调用String的valueOf(Xxx xxx)
		float f1 = 12.3f;
		String str2 = String.valueOf(f1);//"12.3"
		
		Double d1 = new Double(12.4);
		String str3 = String.valueOf(d1);
		System.out.println(str2);
		System.out.println(str3);//"12.4"
		
	}
	
	/*
	 * JDK 5.0 新特性:自动装箱 与自动拆箱
	 */
	@Test
	public void test3(){
//		int num1 = 10;
//		//基本数据类型-->包装类的对象
//		method(num1);
		
		//自动装箱:基本数据类型 --->包装类
		int num2 = 10;
		Integer in1 = num2;//自动装箱
		
		boolean b1 = true;
		Boolean b2 = b1;//自动装箱
		
		//自动拆箱:包装类--->基本数据类型
		System.out.println(in1.toString());
		
		int num3 = in1;//自动拆箱
		
	}
	
	public void method(Object obj){
		System.out.println(obj);
	}
	
	//包装类--->基本数据类型:调用包装类Xxx的xxxValue()
	@Test
	public void test2(){
		Integer in1 = new Integer(12);
		
		int i1 = in1.intValue();
		System.out.println(i1 + 1);
		
		
		Float f1 = new Float(12.3);
		float f2 = f1.floatValue();
		System.out.println(f2 + 1);
	}
	
	//基本数据类型 --->包装类:调用包装类的构造器
	@Test
	public void test1(){
		
		int num1 = 10;
//		System.out.println(num1.toString());
		Integer in1 = new Integer(num1);
		System.out.println(in1.toString());
		
		Integer in2 = new Integer("123");
		System.out.println(in2.toString());
		
		//报异常
//		Integer in3 = new Integer("123abc");
//		System.out.println(in3.toString());
		
		Float f1 = new Float(12.3f);
		Float f2 = new Float("12.3");
		System.out.println(f1);
		System.out.println(f2);
		
		Boolean b1 = new Boolean(true);
		Boolean b2 = new Boolean("TrUe");
		System.out.println(b2);
		Boolean b3 = new Boolean("true123");
		System.out.println(b3);//false
		
		
		Order order = new Order();
		System.out.println(order.isMale);//false
		System.out.println(order.isFemale);//null
	}
	
}

class Order{
	
	boolean isMale;
	Boolean isFemale;
}


import org.junit.Test;

/*
 * 关于包装类使用的面试题
 * 
 * 
 */
public class InterviewTest {

	@Test
	public void test1() {
		Object o1 = true ? new Integer(1) : new Double(2.0);
		System.out.println(o1);// 1.0
		//三目运算符 :  左右类型统一 
	}

	@Test
	public void test2() {
		Object o2;
		if (true)
			o2 = new Integer(1);
		else
			o2 = new Double(2.0);
		System.out.println(o2);// 1

	}

	@Test
	public void test3() {
		Integer i = new Integer(1);
		Integer j = new Integer(1);
		System.out.println(i == j);//false
		
		//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
		//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
		//-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
		
		Integer m = 1;
		Integer n = 1;
		System.out.println(m == n);//true

		Integer x = 128;//相当于new了一个Integer对象
		Integer y = 128;//相当于new了一个Integer对象
		System.out.println(x == y);//false
	}

}

第六章 面向对象 下

6.1 static

  • static:静态的
  • static可以用修饰来修饰:属性、方法、代码块、内部类

static修饰属性

  • 静态变量:static修饰过的变量,这个属性在该类所有创建的对象是共享的。所有对象共享这一个变量。只要其中一个对象更改此变量,当其他对象调用此静态变量时,是修改过了的。
  • static修饰属性的其他说明:
    • 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
    • 静态变量的加载要早于对象的创建。
    • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
    • 能否直接调用类变量(静态变量)实例变量(非静态变量)
      可以不能
      对象可以可以
  • 静态属性举例:System.out; Math.PI;
  • 如何确定一个属性是否要声明为static的?
    • 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
    • 类中的常量也常常声明为static(例如:Math.PI)

static修饰方法

  • 静态方法随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
    能否直接调用静态方法非静态方法
    可以不能
    对象可以可以
  • static注意点:
    • 静态方法不能调用类中非静态的属性或方法。
    • 在静态的方法内,不能使用this关键字、super关键字。
    • 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
  • 如何确定一个方法是否要声明为static的?
    • 操作静态属性的方法,通常设置为static的
    • 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
public class Main{
	public static void main(String[] args){
		//类.静态变量
		System.out.println("Lei.a:"+Lei.a);
		//类.静态方法
		Lei.d();
		Lei.StaticFunc();

		//System.out.println("Lei.b:"+Lei.b);//编译不通过
		//Lei.c();//编译不通过
		
		Lei var_a=new Lei();
		Lei var_b=new Lei();
		System.out.println("\nvar_a.a:"+var_a.a+" var_a.b:"+var_a.b);
		System.out.println("var_b.a:"+var_b.a+" var_b.b:"+var_b.b);
		var_b.a=10;
		var_b.b=20;
		System.out.println("Changed:");
		System.out.println("var_a.a:"+var_a.a+" var_a.b:"+var_a.b);
		System.out.println("var_b.a:"+var_b.a+" var_b.b:"+var_b.b+"\n");
		
		System.out.println("Func:");
		var_a.unStaticFunc();
		var_a.StaticFunc();
	}
}

class Lei{
	public static int a=3;
	public int b=6;
	public static void d(){
		System.out.println("This is func d. It's static.");
	}
	public void c(){
		System.out.println("This is func c.");
	}
	//非静态方法
	public void unStaticFunc(){
	    System.out.println("*************\nThis is unStaticFunc.");
		//调用静态变量
		System.out.println(a);
		//调用非静态变量
		System.out.println(b);
		//调用静态方法
		d();
		//调用非静态方法
		c();
	}
	//静态方法
	public static void StaticFunc(){
	    System.out.println("*************\nThis is StaticFunc.");
		//调用静态变量
		System.out.println(a);
		//调用非静态变量
		//System.out.println(b); //会报错
		//调用静态方法
		d();
		//调用非静态方法
		//c(); //会报错
	}
}

// 运行结果
// Lei.a:3
// This is func d. It's static.
// *************
// This is StaticFunc.
// 3
// This is func d. It's static.

// var_a.a:3 var_a.b:6
// var_b.a:3 var_b.b:6
// Changed:
// var_a.a:10 var_a.b:6
// var_b.a:10 var_b.b:20

// Func:
// *************
// This is unStaticFunc.
// 10
// 6
// This is func d. It's static.
// This is func c.
// *************
// This is StaticFunc.
// 10
// This is func d. It's static.

6.2 单例设计模式

  • 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。

饿汉式

//饿汉式
public class SingletonTest1 {
	public static void main(String[] args) {
//		Bank bank1 = new Bank(); //编译不通过
//		Bank bank2 = new Bank(); //编译不通过
		
		Bank bank1 = Bank.getInstance();
		Bank bank2 = Bank.getInstance();
		System.out.println(bank1 == bank2);
		//true 说明是同一个对象
	}
}
class Bank{
	//1.私有化类的构造器
	private Bank(){
	}
	//2.内部创建类的对象
	//4.要求此对象也必须声明为静态的
	private static Bank instance = new Bank();
	//3.提供公共的静态的方法,返回类的对象
	public static Bank getInstance(){
		return instance;
	}
}

懒汉式

public class SingletonTest2 {
	public static void main(String[] args) {
		Order order1 = Order.getInstance();
		Order order2 = Order.getInstance();
		System.out.println(order1 == order2);
		// true
	}
}
class Order{
	//1.私有化类的构造器
	private Order(){
	}
	//2.声明当前类对象,没有初始化
	//4.此对象也必须声明为static的
	private static Order instance = null;
	//3.声明public、static的返回当前类对象的方法
	public static Order getInstance(){
		if(instance == null){
			instance = new Order();
		}
		return instance;
	}
}

6.3 代码块

  • 代码块的作用:用来初始化类、对象
  • 代码块如果有修饰的话,只能使用static.
  • 一个类可以有多个代码块

静态代码块

  • 随着类的加载而执行,而且只执行一次
  • 作用:初始化类的信息
  • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
  • 静态代码块的执行要优先于非静态代码块的执行
  • 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构

非静态代码块

  • 随着对象的创建而执行
  • 每创建一个对象,就执行一次非静态代码块
  • 作用:可以在创建对象时,对对象的属性等进行初始化
  • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
  • 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
public class Block{
    public static void main (String[] args) {
        ming a=new ming();
        a.get();
        System.out.println("\nAnother");
        ming b=new ming();
        b.get();
    }
}
class ming{
    public static int va=0;
    int vb=0;
    static{
        va=0;
        // vb=1;//编译不通过
        System.out.println("This is static block a.");
    }
    {
        va++;
        vb++;
        System.out.println("This is block b.");
    }
    static{
        System.out.println("This is static block c.");
    }
    {
        System.out.println("This is block d.");
    }
    public ming(){
        System.out.println("This is generator.");
    }
    public void get(){
        System.out.println("A:"+va+" B:"+vb);
    }
}
// 输出
// This is static block a.
// This is static block c.
// This is block b.
// This is block d.
// This is generator.
// A:1 B:1

// Another
// This is block b.
// This is block d.
// This is generator.
// A:2 B:1

练习:静态/非静态代码块与构造器先后顺序

class Father {
	static {
		System.out.println("11111111111");
	}
	{
		System.out.println("22222222222");
	}
	public Father() {
		System.out.println("33333333333");

	}
}

public class Son extends Father {
	static {
		System.out.println("44444444444");
	}
	{
		System.out.println("55555555555");
	}
	public Son() {
		System.out.println("66666666666");
	}

	public static void main(String[] args) { // 由父及子 静态先行
		System.out.println("77777777777");
		System.out.println("************************");
		new Son();
		System.out.println("************************");
		new Son();
		System.out.println("************************");
		new Father();
	}
}

//输出

// 11111111111
// 44444444444
// 77777777777
// ************************
// 22222222222
// 33333333333
// 55555555555
// 66666666666
// ************************
// 22222222222
// 33333333333
// 55555555555
// 66666666666
// ************************
// 22222222222
// 33333333333
//总结:由父及子,静态先行
class Root{
	static{
		System.out.println("Root的静态初始化块");
	}
	{
		System.out.println("Root的普通初始化块");
	}
	public Root(){
		super();
		System.out.println("Root的无参数的构造器");
	}
}
class Mid extends Root{
	static{
		System.out.println("Mid的静态初始化块");
	}
	{
		System.out.println("Mid的普通初始化块");
	}
	public Mid(){
		super();
		System.out.println("Mid的无参数的构造器");
	}
	public Mid(String msg){
		//通过this调用同一类中重载的构造器
		this();
		System.out.println("Mid的带参数构造器,其参数值:"
			+ msg);
	}
}
class Leaf extends Mid{
	static{
		System.out.println("Leaf的静态初始化块");
	}
	{
		System.out.println("Leaf的普通初始化块");
	}	
	public Leaf(){
		//通过super调用父类中有一个字符串参数的构造器
		super("字符串");
		System.out.println("Leaf的构造器");
	}
}
public class LeafTest{
	public static void main(String[] args){
		new Leaf(); 
		System.out.println();
		new Leaf();
	}
}

//输出
// Root的静态初始化块
// Mid的静态初始化块
// Leaf的静态初始化块
// Root的普通初始化块
// Root的无参数的构造器
// Mid的普通初始化块
// Mid的无参数的构造器
// Mid的带参数构造器,其参数值:字符串
// Leaf的普通初始化块
// Leaf的构造器

// Root的普通初始化块
// Root的无参数的构造器
// Mid的普通初始化块
// Mid的无参数的构造器
// Mid的带参数构造器,其参数值:字符串
// Leaf的普通初始化块
// Leaf的构造器

6.4 final

  • final可以用来修饰的结构:类、方法、变量
  • final 用来修饰一个类:此类不能被其他类所继承。比如:String类、System类、StringBuffer类
final class A{
}
class B extends A{
    //Test.java:9: error: cannot inherit from final A
}
  • final 用来修饰方法:表明此方法不可以被重写.比如:Object类中getClass();
class A{
    final void func(){};
}
class B extends A{
    void func(){};
    //Test.java:11: error: func() in B cannot override func() in A
}
  • final 用来修饰变量:此时的"变量"就称为是一个常量。
  • final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
  • final修饰局部变量:
    • 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
public void show(final int num){
	//num = 20;//编译不通过
	System.out.println(num);
}
  • static final 用来修饰属性:全局常量

6.5 abstract

  • abstract可以用来修饰的结构:类、方法

abstract修饰类:抽象类

  • 此类不能实例化,但有构造器,用于子类实例化

abstract修饰方法:抽象方法

  • 抽象方法只有方法的声明,没有方法体(不用写{}
  • 抽象方法只能存在于抽象类中
  • 子类使用抽象方法:
    • 如果子类不是一个抽象类,则必须重写父类的抽象方法,给出方法体
    • 如果子类是一个抽象类,那么不需要重写父类的抽象方法

abstract注意点

  • abstract不能修饰属性、构造器等
  • abstract不能用来修饰私有方法,静态方法,final的方法,final的类。
  • 抽象类是可以定义构造器的

抽象类的匿名子类

public class Main
{
    public static void main(String[] args) {
        Manager mm=new Manager("li",001,100);
        mm.Work();
        CommonEmployee em=new CommonEmployee();
        em.Work();
		
        method(mm);//非匿名对象
        method(new Manager(200));//匿名对象
        Employee mn = new Employee(){
        	public void Work(){
        		System.out.println("匿名的经理在工作");
        	}
        };//创建了一个Employee的匿名子类
          //这个类是抽象类Employee的实现,没有名字
        method2(mn);
    }
    public static void method(Manager m){
    	m.Work();
    }
    public static void method2(Employee m){
    	m.Work();
    }
}

abstract class Employee{
    private String name;
    private int id;
    private double salary;
    public Employee(){
	    super();
    }
    public Employee(String name,int id,double salary){
	    super();
	    this.name=name;
	    this.id=id;
	    this.salary=salary;
    }
    public abstract void Work();
}

class Manager extends Employee{
	private double bonus;
	public void Work(){
		System.out.println("经理管理公司");
	}
	public Manager(double bonus){
		super();
		this.bonus=bonus;
	}
	public Manager(String name,int id,double salary){
		super(name,id,salary);
		this.bonus=bonus;
	}
}

class CommonEmployee extends Employee{
	public void Work(){
		System.out.println("员工工作");
	}
	public CommonEmployee(){
		super();
	}
}

6.7 模板方法设计模式

  • 功能内部一部分是确定的,一部分是不确定的。可以把不确定的部分暴露出来,留给子类以后具体实现。
public class Main
{
    public static void main(String[] args) {
        MyTemplate t = new MyTemplate();
		t.spendTime();
    }
}
abstract class Template{
	//计算某段代码执行所需要花费的时间
	public void spendTime(){
		long start = System.currentTimeMillis();
		this.code();//不确定的部分、易变的部分
		long end = System.currentTimeMillis();
		System.out.println("花费的时间为:" + (end - start));
	}
	public abstract void code();
}

class MyTemplate extends Template{
	@Override
	public void code() {
		//测试的代码
		for(int i = 2;i <= 1000;i++){
			boolean isFlag = true;
			for(int j = 2;j <= Math.sqrt(i);j++){
				
				if(i % j == 0){
					isFlag = false;
					break;
				}
			}
			if(isFlag){
				System.out.println(i);
			}
		}
	}
}

6.8 接口

  • java是单继承,没有像C++那样的多重继承。Java可以使用接口。
  • 有时候,几个类中有相似的特征,需要抽象出来。

接口的使用

  • 定义:interface
  • Java中,接口与类是并列的结构。
  • 如何定义接口:定义接口的成员
    • JDK7之前:只能定义常量与抽象方法。
      • 全局常量: public static final(可省略不写)
      • 抽象方法: public abstract
    • JDK8:还可以继续定义静态方法与默认方法
  • 接口不能定义构造器,即不能实例化!
  • Java中,使用类实现(implements)接口。
    • 如果此实现类覆盖(重写)了接口所有的方法,则此实现类可以实例化。
    • 如果此实现类没有覆盖(重写)了接口所有的方法,则此实现类仍为抽象类。

多实现

  • java可以实现多个接口
  • 格式:class name extends father_class_name interface interface1,interface2.....

例子

public class Main
{
    public static void main(String[] args) {
        System.out.println(Flyable.MAX_SPEED);
        System.out.println(Flyable.MIN_SPEED);
        Plane plane=new Plane();
        plane.fly();
        plane.stop();
        Bullet bullet=new Bullet();
        bullet.fly();
        bullet.stop();
        bullet.attack();
    }
}
interface Flyable{
	//全局常量
	public static final int MAX_SPEED = 7900;
	int MIN_SPEED = 1; //省略写法,仍然是全局常量
	//抽象方法
	public abstract void fly();
	void stop();
}
interface Attackable{
	void attack();
}
interface AttackAndFly extends Flyable,Attackable{
	
}
class Plane implements Flyable{
	public void fly(){
		System.out.println("飞机起飞");
	}
	public void stop(){
		System.out.println("飞机降落");
	}
}
//多实现
class Bullet implements Flyable,Attackable{
	public void fly(){
		System.out.println("子弹发射");
	}
	public void stop(){
		System.out.println("子弹落地");
	}
	public void attack(){
		System.out.println("子弹攻击");
	}
}

接口的继承性

  • 接口与接口之间可以继承,甚至是多继承
  • 接口的使用体现了多态性。实际上是定义了一种规范

匿名

public class USBTest {
	public static void main(String[] args) {
		Computer com = new Computer();
		//1.创建了接口的非匿名实现类的非匿名对象
		Flash flash = new Flash();
		com.transferData(flash);
		//2. 创建了接口的非匿名实现类的匿名对象
		com.transferData(new Printer());
		//3. 创建了接口的匿名实现类的非匿名对象
		USB phone = new USB(){
			@Override
			public void start() {
				System.out.println("手机开始工作");
			}
			@Override
			public void stop() {
				System.out.println("手机结束工作");
			}
		};
		com.transferData(phone);
		//4. 创建了接口的匿名实现类的匿名对象
		com.transferData(new USB(){
				@Override
				public void start() {
					System.out.println("mp3开始工作");
				}

				@Override
				public void stop() {
					System.out.println("mp3结束工作");
				}
			}
		);
	}
}

class Computer{
	public void transferData(USB usb){//USB usb = new Flash();
		usb.start();
		System.out.println("具体传输数据的细节");
		usb.stop();
	}
}

interface USB{
	//常量:定义了长、宽、最大最小的传输速度等
	void start();
	void stop();
}

class Flash implements USB{
	@Override
	public void start() {
		System.out.println("U盘开启工作");
	}
	@Override
	public void stop() {
		System.out.println("U盘结束工作");
	}
}

class Printer implements USB{
	@Override
	public void start() {
		System.out.println("打印机开启工作");
	}
	@Override
	public void stop() {
		System.out.println("打印机结束工作");
	}
}

接口应用:代理(Proxy)模式

  • 为其他对象提供一种代理以控制对这个对象的访问。
public class Main
{
    public static void main(String[] args) {
        Server s=new Server();
        ProxyServer p = new ProxyServer(s);
        p.browse();
    }
}
interface Network{
	public void browse();
}
//被代理类
class Server implements Network{
	public void browse(){
		System.out.println("服务器访问网络");
	}
}
//代理类
class ProxyServer implements Network{
	private Network work;
	public ProxyServer(Network w){
		work=w;
	}
	public void check(){
		System.out.println("检查网络");
	}
	public void browse(){
		check();
		work.browse();
	}
}
  • 应用场景
    • 安全代理:屏蔽对真实角色的直接访问。
    • 远程代理:通过代理类处理远程方法调用(RMI)
    • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
  • 比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
  • 分类
    • 静态代理(静态定义代理类)
    • 动态代理(动态生成代理类)
      • JDK自带的动态代理,需要反射等知识

工厂模式

  • 留空

小题

  • 面试题一:
public class Main
{
    public static void main(String[] args) {
        C c=new C();
        c.px();
    }
}
interface A{
    int x=0;
}
class B{
    int x=0;
}
class C extends B implements A{
    void px(){
        System.out.println(x);
    }
}
- 报错
Main.java:16: error: reference to x is ambiguous
        System.out.println(x);
                           ^
  both variable x in B and variable x in A match 1 error

Java8中的新特性

  • 接口中可以定义静态方法与默认方法。
  • 接口类中静态方法只能通过接口进行调用。实现接口的类是无法调用接口的静态方法的。
    • 直接接口名.静态方法()
  • 实现接口的子类可以调用接口的默认方法的。也可以在类中对其进行重写。
  • 默认接口的功能如下:
    • 情况一:
public class Main
{
    public static void main(String[] args) {
        A a=new A();
        a.method1();//This is 1;
    }
}
interface jiekou{
	default void method1(){
		System.out.println("This is 1");
	}
}
class A implements jiekou{
	
}
- 情况二:如果子类继承的父类和实现的接口中具有同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类方法。
public class Main
{
    public static void main(String[] args) {
        A a=new A();
        a.method1();//This is B;
    }
}
interface jiekou{
	default void method1(){
		System.out.println("This is 1");
	}
}
class B{
	public void method1(){
		System.out.println("This is B");
	}
}
class A extends B implements jiekou{
	
}
  • 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。-->接口冲突。这就需要我们必须在实现类中重写此方法
  • 报错:
public class Main{
    public static void main(String[] args) {}
}

interface A{
	void method();
}
interface B{
	void method();
}
class C implements A,B{
}
  • 重写后不报错:
public class Main{
    public static void main(String[] args) {}
}

interface A{
	void method();
}
interface B{
	void method();
}
class C implements A,B{
	public void method();
}

6.9 内部类

  • Java中允许类A声明在类B中,这样A是内部类,B是外部类。
  • 内部类的分类:
    • 成员内部类
    • 局部内部类(方法、代码块、构造器等内部)
class A{
	{
		class B{
			//局部内部类
		}
	}
	class E{
		//成员内部类
	}
	public void func(){
		class C{
			//局部内部类
		}
	}
	public A(){
		class D{
			//局部内部类
		}
	}
}

成员内部类

  • 作为外部类的成员
    • 可以调用外部类的结构
    • 可以被static修饰
    • 可以被四中不同的权限修饰符修饰
  • 作为一个类
    • 可以声明自己的属性、方法、构造器等。
    • 可以加final
    • 可以加abstract

成员内部类的实例化

Person.Dog dog = new Person.Dog();
//Dog为Perosn类里面的静态成员内部类
//可以不用实例化Person直接实例化Dog
Person p=new Person();
Person.Bird b = p.Bird();
//Bird是Person类里面的非静态成员内部类
//需要先实例化Person再实例化Bird

局部内部类

  • 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,要求此局部变量声明为final的。
    • jdk 7及之前版本:要求此局部变量显式的声明为final的,这样才能为局部内部类使用。
    • jdk 8及之后的版本:可以省略final的声明
public void method(){
		//局部变量
		int num = 10;
		class AA{
			public void show(){
//				num = 20; //编译报错,提示num为final,不可修改
				System.out.println(num);
			}
		}
	}

内部类应用:返回接口的类的对象

//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
	//创建一个实现了Comparable接口的类:局部内部类

	//方式一:
	class MyComparable implements Comparable{
		@Override
		public int compareTo(Object o) {
			return 0;
		}
	}
	return new MyComparable();

	//方式二:
	return new Comparable(){
		@Override
		public int compareTo(Object o) {
			return 0;
		}
	};
}

第七章:异常处理

7.1 异常概述

  • 异常:程序运行时发生的不正常的情况。
    • ERROR:Java虚拟机无法解决的严重问题。(一般不针对此编写代码)
    • Exception:一般性问题。

错误:ERROR

  • ERROR:栈溢出
public class Main
{
    public static void main(String[] args) {
		main(args);
    }
}
//java.lang.StackOverflowError
  • ERROR:堆溢出
public class Main
{
    public static void main(String[] args) {
		int[] array=new int[1024*1024*1024];
    }
}
//java.lang.OutOfMemoryError: Java heap space
  • 错误的处理:
    • 遇到错误程序终止
    • 编写程序时考虑到错误,遇到时进行处理。

异常体系结构

  • java.lang.Throwable
    • java.lang.Error : 一般不针对此编写代码处理
    • java.lang.Exception : 可以进行异常的处理
      • 编译时异常
        • IOException
          • FileNotFoundException
        • ClassNotFoundException
      • 运行时异常
        • ArithmeticException
        • NullPointerException
        • ArrayIndexOutOfBoundsException
        • ClassCastException
        • NumberFormatException
        • InputMismatchException

7.2 常见异常

运行时异常

  • 可以编译,可以生成字节码文件

  • NullPointerException

public class Main
{
    public static void main(String[] args) {
		int[] arr=null;
		arr[3]=1;
    }
}
  • ArrayIndexOutOfBoundsException
public class Main
{
    public static void main(String[] args) {
		int[] arr=new int[3];
		arr[3]=0;
    }
}
  • ClassCastException
public class Main
{
    public static void main(String[] args) {
		Object a=new B();
		String str=(String)a;
    }
}

class B{
}
  • NumberFormatException
public class Main
{
    public static void main(String[] args) {
		String a="abc";
		int num=Integer.parseInt(a);
    }
}
  • ArithmeticException
import java.util.Scanner;
public class Main
{
    public static void main(String[] args) {
		int a=10,b=0;
		int c=a/b;
    }
}

编译时异常

  • 编译时就报错的异常

  • FileNotFoundException……

public class Main
{
    public static void main(String[] args) {
		File f=new File("hello.txt");
		FileInputStream fis=new FileInputStream(f);

		int data=fis.read();
		while(data!=-1){
			System.out.print((char)data);
			data=fis.read();
		}

		fis.close;
    }
}

7.3 异常处理概述

  • 方式一:try-catch-finally
    • 自己解决掉
  • 方式二:throws+异常处理
    • 交给专门部分处理

7.4 异常处理:抓抛模型

  • 过程一:抛
    • 程序在正常执行的过程中,一旦出现异常,在异常代码处生成一个异常类的对象。并将此对象抛出。一旦抛出对象以后,其后的代码不再执行。
  • 过程二:抓
    1. try-catch-finally
    2. throws+异常处理

try-catch-finally

  • 基本结构
try{
	//可能出现异常的代码
}catch(异常类型1, 变量名1){
	//处理异常的方式1
}catch(异常类型2, 变量名2){
	//处理异常的方式2
}catch(异常类型3, 变量名3){
	//处理异常的方式3
}
...
finally{
	//一定会执行的代码
}
  • finally 是可选的
  • 使用 try 把异常代码包装起来,出现异常时,一旦try中异常对象匹配到catch,执行对应的catch进行异常处理。一旦处理完成,跳出try-catch结构
    • catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
    • catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
  • try-catch-finally是可以嵌套的。
  • 显示出错信息:
    1. String getMessage()
    2. printStackTrace()
  • 在try结构中声明的变量,再出了try结构以后,就不能再被调用
    • 可以现在try之前声明变量
  • 使用try-catch-finally处理编译时异常,编译时不报错,但是运行时仍有可能出错。
  • 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
import java.util.Scanner;
public class Main
{
    public static void main(String[] args) {
		int a=10,b=0;
		try{
			int c=a/b;
			System.out.println("Hi!");
		}catch(ArithmeticException e){
			System.out.println("出现异常!");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}finally{
			System.out.println("finally");
		}
    }
}

输出:
/ by zero
finally
java.lang.ArithmeticException: / by zero
	at Main.main(Main.java:7)

finally

  • finally是可选的
  • finally中代码一定会被执行,即使catch里面的代码又出现异常或者try里面有return返回语句
public class Main
{
    public static int func(){
	    int a=10,b=0;
		try{
			int c=a/b;
			System.out.println("Hi!");
			return 1;
		}catch(ArithmeticException e){
			System.out.println("出现异常!");
			return 2;
		}finally{
			System.out.println("finally");
		}
    }
    public static void main(String[] args) {
		System.out.println(func());
    }
}

输出:
出现异常!
finally
2
  • 没有报错,finally也会执行:
public class Main
{
    public static int func(){
	    int a=10,b=0;
		try{
			int c=a/2;
			System.out.println("Hi!");
			return 1;
		}catch(ArithmeticException e){
			System.out.println("出现异常!");
			return 2;
		}finally{
			System.out.println("finally");
		}
    }
    public static void main(String[] args) {
		System.out.println(func());
    }
}

输出:
Hi!
finally
1
public class Main
{
    public static int func(){
		try{
			int c=1;
			System.out.println("Hi!");
			return 1;
		}catch(ArithmeticException e){
			System.out.println("出现异常!");
			return 2;
		}finally{
			System.out.println("finally");
			return 3;
		}
    }
    public static void main(String[] args) {
		System.out.println(func());
    }
}
输出:
Hi!
finally
3
  • 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能释放的,需要手动释放。这部分代码写到finally。(防止出错后资源没有释放。)
FileInputStream fis = null;
try {
	File file = new File("hello1.txt");
	fis = new FileInputStream(file);
	
	int data = fis.read();
	while(data != -1){
		System.out.print((char)data);
		data = fis.read();
	}
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally{
	try {
		if(fis != null)
			fis.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

throw

  • throws+异常类型写在方法的声明处。
  • 代码错误时,生成异常对象,如果与throws后声明异常类型匹配,会停止执行对应异常代码,并抛出异常
  • throws只是将异常抛给了方法的调用者,没有处理异常。
public class ExceptionTest2 {
	public static void main(String[] args){
		try{
			method2();
		}catch(IOException e){
			e.printStackTrace();
		}
//		method3();
	}
	public static void method3(){
		try {
			method2();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void method2() throws IOException{
		method1();
	}
	public static void method1() throws FileNotFoundException,IOException{
		File file = new File("hello1.txt");
		FileInputStream fis = new FileInputStream(file);
		int data = fis.read();
		while(data != -1){
			System.out.print((char)data);
			data = fis.read();
		}
		fis.close();
		System.out.println("hahaha!");
	}
}
  • 如果父类方法没有throws抛出异常,子类重写的方法不能用throws,只能使用try-catch-finally处理异常

方法重写与异常

  • 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
public class OverrideTest {
	public static void main(String[] args) {
		OverrideTest test = new OverrideTest();
		test.display(new SubClass());
	}
	public void display(SuperClass s){
		try {
			s.method();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
class SuperClass{
	public void method() throws IOException{
	}
}
class SubClass extends SuperClass{
	public void method()throws FileNotFoundException{
	}
}

7.5 手动抛出异常

  • 异常对象的产生
    1. 系统生成的异常对象
    2. 手动生成异常对象,并抛出(throw)
class Student{
	private int id;
	public void setID(int id) throws Exception{
		if(id>0){
			this.id=id;
		}
		else{
			//运行时异常
			//throw new RuntimeException("您的输入数据非法!");
			//编译异常
			throw new Exception("您的输入数据非法!");
		}
	}
	public int getID(){
		return id;
	}
}
public class Main
{
    public static void main(String[] args) {
	    Student s=new Student();
	    try{
		    s.setID(-100);
		    System.out.println(s.getID());
	    }catch(Exception e){
		    System.out.println(e.getMessage());
	    }
    }
}

7.6 用户自定义的异常类

  1. 自定义类,继承Exception或RuntimeException
  2. 提供serialVersionUID,序列号。
  3. 提供重载的构造器
class Student{
	private int id;
	public void setID(int id) throws Exception{
		if(id>0){
			this.id=id;
		}
		else{
			//运行时异常
			//throw new RuntimeException("您的输入数据非法!");
			//编译异常
			throw new FuShuException("您的输入数据非法!");
		}
	}
	public int getID(){
		return id;
	}
}
public class Main
{
    public static void main(String[] args) {
	    Student s=new Student();
	    try{
		    s.setID(-100);
		    System.out.println(s.getID());
	    }catch(Exception e){
		    System.out.println(e.getMessage());
	    }
    }
}

class FuShuException extends Exception{
	static final long serialVersionUID = -7034897193246939L;
	public FuShuException(){
	}
	public FuShuException(String msg){
		super(msg);
	}
}

综合练习:除法

public class EcmDef
{
    public static void main(String[] args) {
	    try{
			int i=Integer.parseInt(args[0]);
			int j=Integer.parseInt(args[1]);
		    int result=ecm(i,j);
		    System.out.println(result);
	    }catch(NumberFormatException e){
		    System.out.println("数据类型不一致");
	    }catch(ArrayIndexOutOfBoundsException e){
		    System.out.println("缺少命令行参数");
	    }catch(ArithmeticException e){
		    System.out.println("除0");
	    }catch(EcDef  e){
		    System.out.println(e.getMessage());
	    }
    }
    public static int ecm(int i,int j) throws EcDef{
	    if(i<0||j<0){
		    throw new EcDef("分子或分母为负数!");
	    }
	    return i/j;
    }
}

class EcDef extends Exception{
	static final long serialVersionUID = -7034897193246939L;
	public EcDef(){
	}
	public EcDef(String msg){
		super(msg);
	}
}