Jave基础编程
第一章 Java语言概述
JDK、JRE、JVM关系
- JDK(Java Development Kit Java开发工具包)
JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了
JRE。所以安装了JDK,就不用在单独安装JRE了。
其中的开发工具:编译工具(javac.exe) 打包工具(jar.exe)等 - JRE(Java Runtime Environment Java运行环境)
包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,
如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
简单而言,使用JDK的开发工具完成的java程序,交给JRE去运行。
1 | JDK = JRE + 开发工具集(例如Javac编译工具等) |
第一个程序总结
- java程序编写-编译-运行的过程
编写:我们将编写的java代码保存在以”.java”结尾的源文件中
编译:使用javac.exe命令编译我们的java源文件。格式:javac 源文件名.java
运行:使用java.exe命令解释运行我们的字节码文件。 格式:java 类名 - 在一个java源文件中可以声明多个class。但是,只能最多有一个类声明为public的。而且要求声明为public的类的类名必须与源文件名相同。
- 程序的入口是main()方法。格式是固定的。
- 输出语句:
System.out.println():先输出数据,然后换行
System.out.print():只输出数据 - 每一行执行语句都以”;”结束。
- 编译的过程:编译以后,会生成一个或多个字节码文件。字节码文件的文件名与java源文件中的类名相同。
1 | public class Hello { |
第二章 Java基本语法(上)
基本数据类型
- 整型( byte \ short \ int \ long )
- Java各整数类型有固定的表数范围和字段长度,不受具体OS的影响,以保java程序的可移植性。
- java的整型常量默认为 int 型,声明long型常量须后加‘l’或‘L’
- java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long
- byte
占用空间:1字节 = 8bit位 表数范围:-128 ~ 127 - short
占用空间:2字节 = 16bit位 表数范围:-2^15 ~ 2^15-1 - int
占用空间:4字节 = 32bit位 表数范围:-2^31 ~ 2^31-1(约21亿) - long
占用空间:8字节 = 64bit位 表数范围:-2^63 ~ 2^63-11
2
3
4
5
6
7
8
9
10
11
12public class Demo {
public static void main(String[] args) {
byte b = 0;
short s = 0;
int i = 0;
long l = 0L;
System.out.println("byte型的:" + b);
System.out.println("short型的:" + s);
System.out.println("int型的:" + i);
System.out.println("long型的:" + l);
}
}
- byte
- 浮点型( float \ double )
- 与整数类型类似,Java 浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响。
- 浮点型常量有两种表示形式:
十进制数形式:如:5.12 512.0f .512 (必须有小数点)
科学计数法形式:如:5.12e2 512E2 100E-2- float
float:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。
定义 float 类型变量时,变量要以 “f” 或者 “F” 结尾
占用空间:4字节 = 32bit位 表数范围:-3.403E38 ~ 3.403E38 - double
double:双精度,精度是float的两倍。通常采用此类型。
占用空间:8字节 = 64bit位 表数范围:-1.798E308 ~ 1.798E3081
2
3
4
5
6
7
8public class Demo {
public static void main(String[] args) {
float f = 3.14F;
double d = 3.14;
System.out.println("float型的:" + f);
System.out.println("double型的:" + d);
}
}
- float
- 字符型(char)
- char 型数据用来表示通常意义上“字符”(2字节)
- Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。
- 字符型变量的三种表现形式:
字符常量是用单引号(‘ ’)括起来的单个字符。例如:char c1 = ‘a’; char c2 = ‘中’; char c3 = ‘9’;
Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。例如:char c3 = ‘\n’; // ‘\n’表示换行符
直接使用 Unicode 值来表示字符型常量:‘\uXXXX’。其中,XXXX代表一个十六进制整数。如:\u000a 表示 \n。 - char类型是可以进行运算的。因为它都对应有Unicode码。
1
2
3
4
5
6
7
8
9
10
11
12public class Demo {
public static void main(String[] args) {
char c1 = 'A';
char c2 = 'a';
char c3 = '中';
char c4 = '\n';
System.out.println("char型的c1:" + c1);
System.out.println("char型的c2:" + c2);
System.out.println("char型的c3:" + c3);
System.out.println("char型的c4:" + c4);
}
}
- 布尔型( boolean : true\false )
- boolean 类型用来判断逻辑条件,一般用于程序流程控制:
if条件控制语句;
while循环控制语句;
do-while循环控制语句;
for循环控制语句; - boolean类型数据只允许取值true和false,无null。 不可以使用0或非 0 的整数替代false和true,这点和C语言不同。
- Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达所操作的boolean值,在编译之后都使用java虚拟机中的int数据类型来代替:true用1表示,false用0表示。———《java虚拟机规范 8版》
引用数据类型
- 字符串类型(String)
- String不是基本数据类型,属于引用数据类型
- 使用方式与基本数据类型一致。例如:String str = “abcd”;
- String 可以和 8 种基本的数据类型做运算,运算结果是String,且运算只能是连接运算: + ==>是连接符号,即为拼接在一起
- 一个字符串可以串接另一个字符串,也可以直接串接其他类型的数据。例如:
str = str + “xyz” ;
int n = 100;
str = str + n;1
2
3
4
5
6
7
8
9
10public class Demo {
public static void main(String[] args) {
String s1 = "Hello World";
String s2 = "a";
String s3 = "";
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
自动数据类型转化
- 自动类型转换:容量小的类型自动转换为容量大的数据类型。
数据类型按容量大小排序为: - 有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。
- byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型。
- boolean类型不能与其它数据类型运算。
- 当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类型的值将自动转化为字符串(String)类型。
结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。
注意:此时的容量大小表示的表示数的范围的大小,并非是占用的空间的大小。
特别地:byte、char、short 三种之间做运算用 int 以上的来接收
强制数据类型转换
- 自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符:(),但可能造成精度降低或溢出,格外要注意。
- 通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。
如: String a = “43”; int i = Integer.parseInt(a); boolean类型不可以转换为其它的数据类型。
整型常量默认类型是 int 型
浮点型常量默认类型是 double 型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Demo {
public static void main(String[] args) {
// 会造成精度的损失
double d1 = 3.14;
double d2 = 3.9;
int i2 = (int) d2;
int i1 = (int) d1;
System.out.println(i1); // 3
System.out.println(i2); // 3
// 没有精度的损失
long l1 = 123;
short s2 = (short) l1;
System.out.println(s2);
// 精度损失
int i3=128;
byte b=(byte) i3;
System.out.println(b); // -128
}
}
小练习1
1 | public class Demo { |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
小练习2
1 | public class Demo { |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。
- 算术运算符
注意:
- 如果对负数取模,可以把模数负号忽略不记,如:5%-2=1。 但被模数是负数则不可忽略。此外,取模运算的结果不一定总是整数。
- 对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:int x=3510;x=x/1000*1000; x的结果是3000
- “+”除字符串相加功能外,还能把非字符串转换成字符串.例如:System.out.println(“5+5=”+5+5); //打印结果是 5+5=55
- 赋值运算符
- 符号:=
当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理。
支持连续赋值。 扩展赋值运算符: +=, -=, *=, /=, %=
注意:扩展赋值运算符不会改变数据类型
1
2
3
4
5
6
7
8
9
10
11
12public class Demo {
public static void main(String[] args) {
short a = 5;
//不改变数据类型
a++;
System.out.println(a);
// 不改变数据类型
a += 1;
System.out.println(a);
// a = a + 1; 报错,1 为 int 型
}
}
比较运算符(关系运算符)
- 比较运算符的结果都是boolean型,也就是要么是true,要么是false。
- 比较运算符“==”不能误写成“=” 。
逻辑运算符
- 逻辑运算符用于连接布尔型表达式,在Java中不可以写成3 < x < 6,应该写成 x > 3 & x < 6 。
- “&”和“&&”的区别:
单&时,左边无论真假,右边都进行运算;
双&时,如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算。 - “|”和“||”的区别同理,||表示:当左边为真,右边不参与运算。
- 异或( ^ )与或( | )的不同之处是:当左右都为true时,结果为false。
理解:异或,追求的是“异”!
- 位运算符
位运算是直接对整数的二进制进行的运算
注意:在一定范围内,某个数左移 n 位,即这个数乘以 2 的 n 次方。如:3 << 2 结果为 1
在一定范围内,某个数右移 n 位,即这个数除以 2 的 n 次方。如:16 << 2 结果为 4 ,15 << 2 结果为 3
- 三元运算符
- 格式: (条件表达式)?表达式1:表达式2;
条件表达式为true,运算后的结果是表达式1;
条件表达式为false,运算后的结果是表达式2; - 表达式1和表达式2为同种类型
- 三元运算符与if-else的联系与区别:
三元运算符可简化if-else语句
三元运算符要求必须返回一个结果。
if后的代码块可有多个语句
1.运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序。如上表,上一行运算
符总优先于下一行。
2.只有单目运算符、三元运算符、赋值运算符是从右向左运算的。
1 | //三元运算符示例,比较两个数,输出大的 |
1 | // 三元运算符的嵌套 |
1 | // 判断三个数中最大的 |
第三章 Java基本语法(下)
程序流程控制一
- 流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。
- 其流程控制方式采用结构化程序设计中规定的三种基本流程结构,即:
顺序结构:程序从上到下逐行地执行,中间没有任何判断和跳转。
分支结构:根据条件,选择性地执行某段代码;有if…else和switch-case两种分支语句。
循环结构:根据循环条件,重复性的执行某段代码;有while、do…while、for三种循环语句。
程序流程控制:if-else结构
if语句三种格式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// if
if(条件表达式){
执行代码块;
}
// if-else
if(条件表达式){
执行代码块1;
}
else{
执行代码块2;
}
// if-else if...else
if(条件表达式1){
执行代码块1;
}
else if (条件表达式2){
执行代码块2;
}
……
else{
执行代码块n;
}分支结构:if-else使用说明
1.条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量
2.语句块只有一条执行语句时,一对{}可以省略,但建议保留
3.if-else语句结构,根据需要可以嵌套使用
4.当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略
5.当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓
6.当多个条件是“包含”关系时,“小上大下 / 子上父下”Math.random()方法使用
Math.random() 可以生成 [0.0,1.0) 之间的随机数字
如果想要生成: [a,b] 之间的随机数
公式:(int) (Math.random() * (b - a + 1) + a)
- 程序流程控制:switch-case结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14switch(表达式){
case 常量1:
语句1;
// break;
case 常量2:
语句2;
// break; … …
case 常量N:
语句N;
// break;
default:
语句;
// break;
}switch语句有关规则
1.switch(表达式)中表达式的值必须是下述几种类型之一:byte,short,char,int,枚举 (jdk 5.0),String (jdk 7.0);
2.case子句中的值必须是常量,不能是变量名或不确定的表达式值;
3.同一个switch语句,所有case子句中的常量值互不相同;
4.break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到switch结尾
5.default子句是可任选的。同时,位置也是灵活的。当没有匹配的case时,执行default
程序流程控制二
- 循环结构:在某些条件满足的情况下,反复执行特定代码的功能
- 循环语句分类
for 循环
while 循环
do-while 循环 - 循环语句的四个组成部分
初始化部分(init_statement)
循环条件部分(test_exp)
循环体部分(body_statement)
迭代部分(alter_statement)
程序流程控制:循环结构之for循环
语法格式:1
2
3for (①初始化部分;②循环条件部分;④迭代部分){
③循环体部分
}执行过程:①-②-③-④-②-③-④-②-③-④-…..-②
说明:
②循环条件部分为boolean类型表达式,当值为false时,退出循环
①初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔
④可以有多个变量更新,用逗号分隔1
2
3
4
5
6
7
8
9
10// for 循环经典例子-求 1~100 的和
public class Demo {
public static void main(String[] args) {
int result = 0;
for (int i = 1; i <= 100; i++) {
result += i;
}
System.out.println("result=" + result);
}
}程序流程控制:循环结构之while循环
语法格式:1
2
3
4
5①初始化部分;
while (②循环条件部分) {
③循环体部分;
④迭代部分;
}执行过程:①-②-③-④-②-③-④-②-③-④-…-②
说明:
注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
for循环和while循环可以相互转换1
2
3
4
5
6
7
8
9
10
11
12// while 循环经典案例-求 1~100 的和
public class Demo {
public static void main(String[] args) {
int result = 0;
int i = 1;
while (i <= 100) {
result += i;
i++;
}
System.out.println("result=" + result);
}
}程序流程控制:循环结构之do-while循环
语法格式:1
2
3
4
5①初始化部分;
do {
③循环体部分;
④迭代部分;
} while (②循环条件部分);执行过程:①-③-④-②-③-④-②-③-④-…②
说明:
do-while循环至少执行一次循环体。1
2
3
4
5
6
7
8
9
10
11// do-while 循环经典案例-求 1~100 的和
public class Demo {
public static void main(String[] args) {
int result = 0, i = 1;
do {
result += i;
i++;
} while (i <= 100);
System.out.println("result=" + result);
}
}- 程序流程控制:嵌套循环结构
- 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环或内层循环。
- 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。
- 设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次
1
2
3
4
5
6
7
8
9
10
11// 嵌套循环经典例子-打印九九乘法表
public class Demo {
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.printf("%d*%d=%d\t", j, i, i * j);
}
System.out.println();
}
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12D:\PATH-EN\java-path\bin\java.exe ...
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
进程已结束,退出代码01
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 判断100以内的质数
public class isPrimeNumber {
public static void main(String[] args) {
for (int i = 2; i <= 100; i++) {
boolean isFlag = true;
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
isFlag = false;
break;
}
}
if (isFlag == true) {
System.out.printf("%d is a prime number\n", i);
}
}
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9D:\PATH-EN\java-path\bin\java.exe ...
2 is a prime number
3 is a prime number
5 is a prime number
.......
......
97 is a prime number
进程已结束,退出代码0特殊关键字的使用
程序流程控制:break的使用
break 语句:break语句用于终止某个语句块的执行1
2
3
4
5{
...
break;
...
}break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
1
2
3
4
5
6
7
8
9
10
11label1:
{ ......
label2:
{ ......
label3:
{ ......
break label2;
......
}
}
}程序流程控制:continue的使用
continue 语句:
1.continue只能使用在循环结构中
2.continue语句用于跳过其所在循环语句块的一次执行,继续下一次循环
3.continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环1
2
3
4
5
6
7
8
9
10
11// continue语句用法举例
// 打印 100以内10的倍数
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i % 10 != 0)
continue;
System.out.println(i);
}
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13D:\PATH-EN\java-path\bin\java.exe ...
0
10
20
30
40
50
60
70
80
90
进程已结束,退出代码01
2
3
4
5
6
7
8
9
10
11
12
13// 求100以内的质数的优化
public class Demo {
public static void main(String[] args) {
label1:
for (int i = 2; i <= 100; i++) {
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0)
continue label1;
}
System.out.printf("%d is a prime number\n", i);
}
}
}程序流程控制:return的使用
- return 关键字并不是专门用于结束循环的,return 语句用于终止函数的执行或退出类的方法,并把控制权返回该方法的调用者。如果这个方法带有返回类型,return 语句就必须返回这个类型的值;如果这个方法没有返回值,可以使用没有表达式的 return 语句。
- 与break和continue不同的是,return直接结束整个方法,不管这个return处于多少层循环之内
1.break只能用于switch语句和循环语句中。
2.continue 只能用于循环语句中。
3.二者功能类似,但continue是终止本次循环,break是终止本层循环。
4.break、continue之后不能有其他的语句,因为程序永远不会执行其后的语句。
5.标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面。
6.很多语言都有goto语句,goto语句可以随意将控制转移到程序中的任意一条语句上,然后执行它。但使程序容易出错。Java中的break和continue是不同于goto的。
小项目
模拟实现一个基于文本界面的《家庭记账软件》
点击查看Utility.java源代码
1 | package exer; |
点击查看FamilyAccount.java源代码
1 | package exer; |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
第四章 数组
- 数组(Array):是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理
- 数组的常见概念:数组名; 下标(或索引); 元素; 数组的长度
- 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
- 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。
- 数组的长度一旦确定,就不能修改。
- 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
- 数组的分类:
按照维度:一维数组、二维数组、三维数组、…
按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)
一维数组的使用
一维数组的使用:声明
一维数组的声明方式:type var[] 或 type[] var;
例如:1
2
3
4int a[];
int[] a1;
double b[];
String[] c; //引用类型变量数组Java语言中声明数组时不能指定其长度(数组中元素的数), 例如: int a[5]; //非法
一维数组的使用:初始化
动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行1
2
3
4
5
6
7
8
9
10
11// 动态初始化
int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;
String names[];
names = new String[3];
names[0] = “Jack”;
names[1] = “Tom”;
names[2] = “Jermyn”;静态初始化:在定义数组的同时就为数组元素分配空间并赋值。
1
2
3
4
5
6// 静态初始化
int arr[] = new int[]{ 3, 9, 8};
或
int[] arr = {3,9,8};
String names[] = {“Jack”,“Tom”,“Jermyn” }一维数组的使用:数组元素的引用
- 定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
- 数组元素的引用方式:数组名[数组元素下标]
- 数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
- 数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]
- 每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长度(元素个数)
- 数组一旦初始化,其长度是不可变的
一维数组的使用:数组元素的默认初始化值
数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。例如:1
2
3
4
5
6public class Demo {
public static void main(String argv[]) {
int a[] = new int[5];
System.out.println(a[3]); //a[3]的默认值为0
}
}对于基本数据类型而言,默认初始化值各有不同
对于引用数据类型而言,默认初始化值为null(注意与0不同!)一维数组的使用:数组元素的默认初始化值
数组元素类型 | 元素默认初始值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0或写为 ‘\u0000’(表现为空) |
boolean | false |
引用类型 | null |
多维数组的使用
- Java 语言里提供了支持多维数组的语法。
对于二维数组的理解,我们可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。其实,从数组底层的运行机制来看,其实没有多维数组。
动态初始化:int[][] arr = new int[3][2];
定义了名称为arr的二维数组。二维数组中有3个一维数组。每一个一维数组中有2个元素。
一维数组的名称分别为arr[0], arr[1], arr[2]
给第一个一维数组1脚标位赋值为78写法是:arr[0][1] = 78;
动态初始化:int[][] arr = new int[3][];
二维数组中有3个一维数组。每个一维数组都是默认初始化值null (注意:区别于格式1)。可以对这个三个一维数组分别进行初始化
arr[0] = new int[3]; arr[1] = new int[1]; arr[2] = new int[2];
注:int[][]arr = new int[][3]; //非法**
定义一个名称为arr的二维数组,二维数组中有三个一维数组
每一个一维数组中具体元素也都已初始化
第一个一维数组 arr[0] = {3,8,2};
第二个一维数组 arr[1] = {2,7};
第三个一维数组 arr[2] = {9,0,1,6};
第三个一维数组的长度表示方式:arr[2].length;
**注意:特殊写法情况:int[] x,y[]; x是一维数组,y是二维数组。
Java中多维数组不必都是规则矩阵形式
- 遍历二维数组
1
2
3
4
5
6
7
8
9
10
11// 遍历二维数组
public class Demo {
public static void main(String[] args) {
int[][] arry = new int[][]{{3, 8, 2}, {2, 7}, {9, 0, 1, 6}};
for (int i = 0; i < arry.length; i++) {
for (int j = 0; j < arry[i].length; j++) {
System.out.print(arry[i][j]+" ");
}
}
}
}小练习1
使用二维数组打印一个 10 行杨辉三角。
【提示】- 第一行有 1 个元素, 第 n 行有 n 个元素
- 每一行的第一个元素和最后一个元素都是 1
- 从第三行开始, 对于非第一个元素和最后一个元素的元素。即:yangHui[i][j] = yangHui[i-1][j-1] + yangHui[i-1][j];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 打印10行杨辉三角
public class YangHui {
public static void main(String[] args) {
int[][] yangHui = new int[10][];
for (int i = 0; i < 10; 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();
}
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13D:\PATH-EN\java-path\bin\java.exe ...
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
Process finished with exit code 0
第五章 面向对象编程(上)
- 程序员从面向过程的执行者转化成了面向对象的指挥者
- 面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
Java语言的基本元素:类和对象
- 面向对象的思想概述
- 类(Class)和对象(Object)是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
- “万事万物皆对象
属 性:对应类中的成员变量
行 为:对应类中的成员方法
Field = 属性 = 成员变量,Method = (成员)方法 = 函数
- 类的语法格式说明:修饰符public:类可以被任意访问的正文要用{ }括起来
1
2
3
4修饰符 class 类名 {
属性声明;
方法声明;
}1
2
3
4
5
6public class Person{
private int age ; //声明私有变量 age
public void showAge(int i) { //声明方法showAge( )
age = i;
}
} - 创建Java自定义类
步骤:
- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
对象的创建和使用
创建对象语法: 类名 对象名 = new 类名();
使用“对象名.对象成员”的方式访问对象成员(包括属性和方法)
过程:
- 创建类,设计类的成员
- 创建类的对象
- 通过“对象.属性”或“对象.方法”调用对象的结构
点击查看示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 例子
public class PersonTest {
public static void main(String[] args) {
//2. 创建Person类的对象 p1
Person p1 = new Person();
//Scanner scanner = new Scanner(System.in);
//调用对象的结构:属性、方法
//调用属性:“对象.属性”
p1.name = "Jermyn";
p1.isMale = true;
//调用方法:“对象.方法”
p1.eat();
p1.sleep();
p1.talk("Chinese");
// 创建一个新的对象 p2
Person p2 = new Person();
p2.run();
System.out.println(p2.name);//null
System.out.println(p2.isMale);
}
}
//1.创建类,设计类的成员
class Person {
//属性
String name;
int age = 1;
boolean isMale;
//方法
public void eat() {
System.out.println(name + "正在吃饭");
}
public void sleep() {
System.out.println(name + "正在睡觉");
}
public void run() {
System.out.println(name + "正在跑步");
}
public void talk(String language) {
System.out.println("人可以说话,使用的是:" + language);
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9D:\PATH-EN\java-path\bin\java.exe ...
Jermyn正在吃饭
Jermyn正在睡觉
人可以说话,使用的是:Chinese
null正在跑步
null
false
Process finished with exit code 0类的访问机制:
- 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。 (例外:static方法访问非static,编译不通过。)
- 在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
- 对象的创建和使用:内存解析
- 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
- 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 对象的创建和使用:匿名对象
- 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
如:new Person().shout(); - 使用情况
- 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
点击查看示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52// 匿名对象的使用
public class Demo {
public static void main(String[] args) {
// 正常的操作
Phone p = new Phone();
// p = null;
System.out.println(p);
p.sendEmail();
p.playGame();
//匿名对象
// new Phone().sendEmail();
// new Phone().playGame();
// 相当于创建两个对象
new Phone().price = 1999;
new Phone().showPrice();//0.0
PhoneMall mall = new PhoneMall();
// mall.show(p);
//匿名对象的使用
mall.show(new Phone());
}
}
class PhoneMall {
public void show(Phone phone) {
phone.sendEmail();
phone.playGame();
}
}
class Phone {
double price;
public void sendEmail() {
System.out.println("发送邮件");
}
public void playGame() {
System.out.println("玩游戏");
}
public void showPrice() {
System.out.println("手机价格为:" + price);
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9D:\PATH-EN\java-path\bin\java.exe ...
test.Phone@1b6d3586
发送邮件
玩游戏
手机价格为:0.0
发送邮件
玩游戏
Process finished with exit code 0
类的成员之一:属性(field)
基本
语法格式:修饰符 数据类型 属性名 = 初始化值 ;
说明1: 修饰符
常用的权限修饰符有:private、缺省、protected、public
其他修饰符:static、final (暂不考虑)
说明2:数据类型
任何基本数据类型(如int、Boolean) 或 任何引用数据类型。
说明3:属性名
属于标识符,符合命名规则和规范即可1
2
3
4
5// 举例
public class Person {
private int age; //声明private变量 age
public String name = “Jermyn”; //声明public变量 name
}变量的分类:成员变量与局部变量
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部声明的变量称为局部变量。
- 所有变量
- 成员变量
- 实例变量(不以static修饰)
- 类变量(以static修饰)
- 局部变量
- 形参(方法、构造器中定义的变量)
- 方法局部变量(在方法内定义)
- 代码块局部变量(在代码块内定义)
- 成员变量
- 注意:二者在初始化值方面的异同:
同:都有生命周期
异:局部变量除形参外,均需显式初始化
- 成员变量(属性)和局部变量的区别
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部,代码块内部,构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间 或 静态域内 | 栈空间 |
- 对象属性的默认初始化赋值
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型,如上面的Person及前面讲过的数组。
成员变量类型 | 初始值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0或写为 ‘\u0000’(表现为空) |
boolean | false |
引用类型 | null |
类的成员之二:方法(method)
什么是方法(method、函数)
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里。
1
2
3
4
5
6
7
8
9
10
11public class Person {
private int age;
public int getAge() { //声明方法getAge()
return age;
}
public void setAge(int i) { //声明方法setAge
age = i; //将参数i的值赋给类的成员变量age
}
}
方法的声明格式
1
2
3
4修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2, ….){
方法体程序代码
return 返回值;
}修饰符:public,缺省,private, protected等
返回值类型:
1.没有返回值:void。
2.有返回值,声明出返回值的类型。与方法体中“return 返回值”搭配使用
方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
形参列表:可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开
返回值:方法在执行完毕后返还给调用它的程序的数据。
- 方法的分类:按照是否有形参及返回值
无返回值 | 有返回值 | |
---|---|---|
无形参 | void 方法名(){} | 返回值的类型 方法名(){} |
有形参 | void 方法名(形参列表){} | 返回值的类型 方法名(形参列表){} |
方法的调用
- 方法通过方法名被调用,且只有被调用才会执行。
方法调用过程分析
注 意:
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法或属性,不可以在方法内部定义方法。
对象数组题目:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49package test;
public class StudentTest {
public static void main(String[] args) {
Student[] stus = new Student[20];
for (int i = 0; i < stus.length; i++) {
stus[i] = new Student();
stus[i].number = i + 1;
stus[i].score = (int) (Math.random() * 101);
stus[i].state = (int) (Math.random() * 6 + 1);
stus[i].showStudentInfo();
}
System.out.println("-------------------------分割线--------------------------");
System.out.println("三年级的学生");
for (int i = 0; i < stus.length; i++) {
if (stus[i].state == 3) {
stus[i].showStudentInfo();
}
}
System.out.println("-------------------------分割线--------------------------");
System.out.println("按学生成绩排序");
for (int i = 0; i < stus.length - 1; i++) {
for (int j = 0; j < stus.length - 1; j++) {
if (stus[j].score>stus[j+1].score){
Student temp = stus[j];
stus[j] = stus[j + 1];
stus[j + 1] = temp;
}
}
}
for (int i = 0; i < stus.length; i++) {
stus[i].showStudentInfo();
}
}
}
class Student {
int number;
int state;
int score;
public void showStudentInfo() {
System.out.println("学号:" + number + "\t成绩:" + score + "\t年级:" + state);
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54D:\PATH-EN\java-path\bin\java.exe ...
学号:1 成绩:13 年级:3
学号:2 成绩:27 年级:3
学号:3 成绩:5 年级:6
学号:4 成绩:19 年级:3
学号:5 成绩:3 年级:3
学号:6 成绩:63 年级:5
学号:7 成绩:95 年级:2
学号:8 成绩:4 年级:6
学号:9 成绩:48 年级:4
学号:10 成绩:67 年级:3
学号:11 成绩:2 年级:3
学号:12 成绩:58 年级:1
学号:13 成绩:10 年级:3
学号:14 成绩:21 年级:6
学号:15 成绩:77 年级:5
学号:16 成绩:64 年级:5
学号:17 成绩:27 年级:2
学号:18 成绩:37 年级:1
学号:19 成绩:81 年级:2
学号:20 成绩:78 年级:5
-------------------------分割线--------------------------
三年级的学生
学号:1 成绩:13 年级:3
学号:2 成绩:27 年级:3
学号:4 成绩:19 年级:3
学号:5 成绩:3 年级:3
学号:10 成绩:67 年级:3
学号:11 成绩:2 年级:3
学号:13 成绩:10 年级:3
-------------------------分割线--------------------------
按学生成绩排序
学号:11 成绩:2 年级:3
学号:5 成绩:3 年级:3
学号:8 成绩:4 年级:6
学号:3 成绩:5 年级:6
学号:13 成绩:10 年级:3
学号:1 成绩:13 年级:3
学号:4 成绩:19 年级:3
学号:14 成绩:21 年级:6
学号:2 成绩:27 年级:3
学号:17 成绩:27 年级:2
学号:18 成绩:37 年级:1
学号:9 成绩:48 年级:4
学号:12 成绩:58 年级:1
学号:6 成绩:63 年级:5
学号:16 成绩:64 年级:5
学号:10 成绩:67 年级:3
学号:15 成绩:77 年级:5
学号:20 成绩:78 年级:5
学号:19 成绩:81 年级:2
学号:7 成绩:95 年级:2
Process finished with exit code 0
再谈方法
再谈方法1:方法的重载(overload)
重载的概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
重载的特点:与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
重载示例:1
2
3
4
5
6//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个小数的和
double add(double x,double y){return x+y;}1.编写程序,定义三个重载方法并调用。方法名为mOL。
三个方法分别接收一个int参数、两个int参数、一个字符串参数。
分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。
在主类的main ()方法中分别用参数区别调用三个方法。2.定义三个重载方法max(),
第一个方法求两个int值中的最大值,
第二个方法求两个double值中的最大值,
第三个方法求三个double值中的最大值,
并分别调用三个方法。点击查看结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38public class OverLoad {
public static void main(String[] args) {
OverLoad test = new OverLoad();
test.mOL(1);
test.mOL(2, 4);
test.mOL("Jermyn");
System.out.println(test.max(23, 12));
System.out.println(test.max(23.3, 23, 2));
System.out.println(test.max(34.2, 34.3, 34.2));
}
//1. 如下的三个方法构成重载
public void mOL(int i) {
System.out.println(i * i);
}
public void mOL(int i, int j) {
System.out.println(i * j);
}
public void mOL(String s) {
System.out.println(s);
}
//2.如下的三个方法构成重载
public int max(int i, int j) {
return (i > j) ? i : j;
}
public double max(double d1, double d2) {
return (d1 > d2) ? d1 : d2;
}
public double max(double d1, double d2, double d3) {
double max = (d1 > d2) ? d1 : d2;
return (max > d3) ? max : d3;
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9D:\PATH-EN\java-path\bin\java.exe ...
1
8
Jermyn
23
23.3
34.3
Process finished with exit code 0再谈方法2:可变个数的形参
JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);s
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);说明:
- 声明格式:方法名(参数的类型名 …参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在一个方法的形参位置,最多只能声明一个可变个数形参
再谈方法3:方法参数的值传递机制
- 方法,必须由其所在类或对象调用才有意义。若方法含有参数:
形参:方法声明时的参数
实参:方法调用时实际传给形参的参数值 - Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
- 再谈方法4:递归(recursion)方法
递归方法:一个方法体内调用它自身。- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 示例:递归方法求1-100的和
public class Demo {
public static void main(String[] args) {
Demo test = new Demo();
System.out.println(test.getSum(100));
}
public int getSum(int i) {
if (i == 1) {
return 1;
} else {
return i + getSum(i - 1);
}
}
}
面向对象特征之一:封装与隐藏
- 程序设计追求“高内聚,低耦合”:
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 :仅对外暴露少量的方法用于使用。 - 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
- 使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。
信息的封装和隐藏
Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改,增强代码的可维护性;
四种访问权限修饰符
Java权限修饰符public、protected、(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限
对于class的权限修饰只可以用public和default(缺省)。
public类可以在任意地方被访问。
default类只可以被同一个包内部的类访问。
类的成员之三:构造器(或构造方法)
构造器的特征
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
构造器的作用:创建对象;给对象进行初始化
- 如:Order o = new Order(); Person p = new Person(“Peter”,15);
- 如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
语法格式:
1
2
3修饰符 类名 (参数列表) {
初始化语句;
}根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
注 意:
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
构造器重载
- 构造器一般用来创建对象的同时初始化对象。如
1
2
3
4
5
6
7
8class Person{
String name;
int age;
public Person(String n , int a){
name=n;
age=a;
}
} - 构造器重载使得对象的创建更加灵活,方便创建各种不同的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 构造器重载举例:
public class Person{
public Person(String name, int age, Date d) {
this(name,age);
…
}
public Person(String name, int age) {
…
}
public Person(String name, Date d) {
…
}
public Person(){
…
}
} - 构造器重载,参数列表必须不同
- 总结:属性赋值过程
截止到目前,我们讲到了很多位置都可以对类的属性赋值。现总结这几个位置,并指明赋值的先后顺序。
赋值的位置:
1.默认初始化
2.显式初始化
3.构造器中初始化
4.通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序:① - ② - ③ - ④
小练习
编写两个类,TriAngle和TriAngleTest,其中TriAngle类中声明私有的底边长base和高height,同时声明公共方法访问私有变量。此外,提供类必要的构造器。另一个类中使用这些公共方法,计算三角形的面积。
点击查看TriAngleTest.java源码
1 | public class TriAngleTest { |
点击查看TriAngle.java源码
1 | public class TriAngle { |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
- 拓展知识:JavaBean
- JavaBean是一种Java语言写成的可重用组件。
所谓javaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
- 拓展知识:UML类图
- +表示 public 类型, - 表示 private 类型,#表示protected类型
- 方法的写法: 方法的类型(+、-) 方法名(参数名: 参数类型):返回值类型
关键字:this的使用
- this是什么?
- 在Java中,this关键字比较难理解,它的作用和其词义很接近。
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
- this 可以调用类的属性、方法和构造器
- 什么时候使用this关键字呢?
- 当在方法内需要用到调用该方法的对象时,就用this。具体的:我们可以用this来区分属性和局部变量。比如:this.name = name;
- 使用this,调用属性、方法
- 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。
- 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量
- 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 调用属性,方法示例
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public void getInfo(){
System.out.println("姓名:" + name) ;
this.speak();
}
public void speak(){
System.out.println(“年龄:” + this.age);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class Person{ // 定义Person类
String name;
Person(String name){
this.name = name;
}
public void getInfo(){
System.out.println("Person类 --> " + this.name) ;
}
// 此方法目的是比较连个实例对象的 name 属性是否相等,this 指的是 测试类中的per1,p值的是per2
// 可以理解为,谁调用了方法谁就是此方法中的 this
// 示意图可看下图
public boolean compare(Person p){
return this.name==p.name;
}
}
public class PersonTest{
public static void main(String args[]){
Person per1 = new Person("张三") ;
Person per2 = new Person("李四") ;
per1.getInfo() ; // 当前调用getInfo()方法的对象是per1
per2.getInfo() ; // 当前调用getInfo()方法的对象是per2
boolean b = per1.compare(per2);
}
}点击查看运行结果
1
2
3
4
5
6D:\PATH-EN\java-path\bin\java.exe ...
Person类 --> 张三
Person类 --> 李四
false
Process finished with exit code 0
- 使用this调用本类的构造器
4.this可以作为一个类中构造器相互调用的特殊格式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Person{ // 定义Person类
private String name ;
private int age ;
public Person(){ // 无参构造器
System.out.println("新对象实例化") ;
}
public Person(String name){
this(); // 调用本类中的无参构造器
this.name = name ;
}
public Person(String name,int age){
this(name) ; // 调用有一个参数的构造器
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age ;
}
}
public class PersonTest{
public static void main(String args[]){
Person person01 = new Person("张三", 18);
System.out.println(person01.getInfo());
}
}点击查看运行结果
1
2
3
4
5D:\PATH-EN\java-path\bin\java.exe ...
新对象实例化
姓名:张三,年龄:18
Process finished with exit code 0注意:
- 可以在类的构造器中使用”this(形参列表)”的方式,调用本类中重载的其他的构造器!
- 明确:构造器中不能通过”this(形参列表)”的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了”this(形参列表)”
- “this(形参列表)”必须声明在类的构造器的首行!
- 在类的一个构造器中,最多只能声明一个“this(形参列表)”
练习一
添加必要的构造器,综合应用构造器的重载,this关键字
Boy
-name:String
-age:int
+setName(i: String)
+getName(): String
+setAge(i: int) +getAge(): int
+marry(girl:Girl)
+shout():void点击查看Boy.java源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class Boy {
private String name;
private int age;
public Boy() {
}
public Boy(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;
}
public void marry(Girl girl) {
System.out.printf("我想娶一个女孩"+girl.getName());
}
public void shout() {
if(this.age >= 22){
System.out.println("你可以去合法登记结婚了!");
}else{
System.out.println("先多谈谈恋爱~~");
}
}
}
Girl
-name:String
-age:int
+setName(i: String)
+getName(): String
+marry(boy:Boy)
+compare(girl:Girl)点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public class Girl {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Girl() {
}
public Girl(String name, int age) {
this.name = name;
this.age = age;
}
public void marry(Boy boy) {
System.out.println("我想嫁给" + boy.getName());
boy.marry(this);
}
public int compare(Girl girl) {
if (this.age > girl.age) {
return 1;
} else if (this.age < girl.age) {
return -1;
} else {
return 0;
}
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class GirlAndBoyTest {
public static void main(String[] args) {
Boy boy = new Boy("罗密欧", 19);
boy.shout();
Girl girl = new Girl("朱丽叶", 18);
girl.marry(boy);
Girl girl1 = new Girl("朱丽叶妹妹", 17);
System.out.println("\n------------------------分割线--------------------------");
if (girl.compare(girl1) > 0) {
System.out.println(girl.getName() + "说,我比" + girl1.getName() + "大");
} else if (girl.compare(girl1) < 0) {
System.out.println(girl.getName() + "说,我比" + girl1.getName() + "小");
} else {
System.out.println(girl.getName() + "说,我和" + girl1.getName() + "一样大");
}
}
}点击查看运行结果
1
2
3
4
5
6
7
8D:\PATH-EN\java-path\bin\java.exe ...
先多谈谈恋爱~~
我想嫁给罗密欧
我想娶一个女孩朱丽叶
------------------------分割线--------------------------
朱丽叶说,我比朱丽叶妹妹大
Process finished with exit code 0
小练习二
按照如下的 UML 类图,创建相应的类,提供必要的结构
在提款方法 withdraw()中,需要判断用户余额是否能够满足提款数额的要求,如果不能,
应给出提示。deposit()方法表示存款。
点击查看Account.java源代码
1 | public class Account { |
按照如下的 UML 类图,创建相应的类,提供必要的结构
点击查看Customer.java
1 | public class Customer { |
按照如下的 UML 类图,创建相应的类,提供必要的结构
- addCustomer 方法必须依照参数(姓,名)构造一个新的 Customer 对象,然后把它放到 customer 数组中。还必须把 numberOfCustomer 属性的值加 1。
- getNumOfCustomers 方法返回 numberofCustomers 属性值。
- getCustomer 方法返回与给出的 index 参数相关的客户。
点击查看Bank.java源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class Bank {
private Customer[] customers;
private int numberOfCustomers;
public Bank() {
customers = new Customer[10];
}
public void addCustomer(String f, String l) {
Customer customer = new Customer(f, l);
// new 一个新的customer ,numberOfCustomers+1 ,即为当前bank里面有多少个账户
// 每 new 一个新的customer存放在数组customers里面
customers[numberOfCustomers++] = customer;
}
public int getNumberOfCustomer() {
return numberOfCustomers;
}
public Customer getCustmoer(int index) {
// 判断查询的customers里面的customer的index,如果小于0和大于当前的账户数,即为空
if (index >= 0 && index < numberOfCustomers) {
return customers[index];
}
return null;
}
}
创建 BankTest 类,进行测试。
点击查看BankTest.java源代码
1 | public class BankTest { |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
关键字:package、import的使用
- package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:
1
2
3
4
5
6
7
8// package 顶层包名.子包名 ;
pack1\pack2\PackageTest.java
package pack1.pack2; //指定类PackageTest属于包pack1.pack2
public class PackageTest{
public void display(){
System.out.println("in method display()");
}
} - 包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;
- 包通常用小写单词标识。通常使用所在公司域名的倒置
- 包的作用:
- 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 包可以包含类和子包,划分项目层次,便于管理
- 解决类命名冲突的问题
- 控制访问权限
例:某航运软件系统包括:一组域对象、GUI和reports子系统包的作用:
- MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程
序的耦合性。
模型层 model 主要处理数据
- 数据对象封装 model.bean/domain
- 数据库操作类 model.dao
- 数据库 model.db
控制层 controller 处理业务逻辑
- 应用界面相关 controller.activity
- 存放fragment controller.fragment
- 显示列表的适配器 controller.adapter
- 服务相关的 controller.service
- 抽取的基类 controller.base
视图层 view 显示数据
- 相关工具类 view.utils
- 自定义view view.ui
- JDK中主要的包介绍
- java.lang——包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
- java.net——包含执行与网络相关的操作的类和接口。
- java.io ——包含能提供多种输入/输出功能的类。
- java.util——包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
- java.text——包含了一些java格式化相关的类
- java.sql——包含了java进行JDBC数据库编程的相关类/接口
- java.awt——包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
- 关键字—import
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。1
2
3
4
5
6
7
8
9// import 包名. 类名;
import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
public class PackTest{
public static void main(String args[]){
Test t = new Test(); //Test类在pack1.pack2包中定义
t.display();
}
} - 注意
- 在源文件中使用import显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可
- 举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
- import static组合的使用:调用指定类或接口下的静态的属性或方法
第六章 面向对象编程(中)
面向对象特征之二:继承性(inheritance)
为描述和处理个人信息,定义类Person:
1 | class Person { |
为描述和处理学生信息,定义类Student:
1 | class Student { |
通过继承,简化Student类的定义:
1 | class Person { |
Student类继承了父类Person的所有属性和方法,并增加了一 个属性school。Person中的属性和方法,Student都可以使用。
- 为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可。 - 此处的多个类称为子类(派生类),单独的这个类称为父类(基类 或超类)。可以理解为:“子类 is a 父类”
- 类继承语法规则:class Subclass extends SuperClass{ }
作用:
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
注意:不要仅为了获取其他类中某个功能而去继承
子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
关于继承的规则:子类不能直接访问父类中私有的(private)的成员变量和方法。Java只支持单继承和多层继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
方法的重写(override/overwrite)
定义:
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法重载和重写的区别
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。 它们的调用地址在编译期就绑定了 。 Java的重载是可以包括父类和子类的,即子类可以重载父类 的同名不同参数的方法。
所以:
对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为 “早绑定”或“静态绑定
而
对于多态,只有等到方法调用的那一刻解释运行器才会确定所要调用的具体方法,这称为 “晚绑定”或“动态绑定 。
引用一句Bruce Eckel 的话: :“不要犯傻,如果它不是晚绑定它就不是多态。”
要求:
1、子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
2、子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
3、子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
4、子类不能重写父类中声明为private权限的方法
5、子类方法抛出的异常不能大于父类被重写方法的异常注意:
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。- 实例
1
2
3
4
5
6
7
8public class Person {
public String name;
public int age;
public String getInfo() {
return "Name:" + name + "\n" + "age:" + age;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Student extends Person {
public String school;
// 重写父类 Person 中的 getInfo 方法
public String getInfo() {
return "Name:" + name + "\n" + "age:" + age + "\t"+ "school:" + school;
}
public static void main(String[] args) {
Student student = new Student();
student.name = "Jermyn";
student.age = 20;
student.school = "USTC";
System.out.println(student.getInfo());
}
}点击查看运行结果
1
2
3
4
5D:\PATH-EN\java-path\bin\java.exe ...
Name:Jermyn
age:20 school:USTC
Process finished with exit code 0
关键字: super
在 Java 类中使用 super 来调用父类中的指定操作:
- super 可用于访问父类中定义的属性
- super 可用于调用父类中定义的成员方法
- super 可用于在子类构造器中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时可以用 super 表明调用的是父类中的成员
- super 的追溯不仅限于直接父类
- super 和 this 的用法相像 this 代表本类对象的引用 super 代表父类的内存空间的标识
调用父类的构造器
- 子类中所有的构造器默认都会访问父类中参数的构造器
- 当父类中没有空参数的构造器时 子类的构造器必须通过 this(参数列表) 或者 super(参数列表) 语句指定调用本类或者父类中相应的造器。同时只能二选一且必须放在构造器的首行
- 如果子类构造器中既未显式调用父类或本类的构造器且父类中又没有无参的构造器则编译出错
this和super的区别
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没 有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没 有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造 器的首行 | 调用父类构造器,必须 放在子类构造器的首行 |
面向对象特征之三:多态性(Polymorphism)
- 对象的多态性:父类的引用指向子类的对象可以直接应用在抽象类和接口上
- Java 引用变量有两个类型:编译时类型 和 运行时类型 。 编译时类型由声明该变量时使用的类型决定 运行时类型由实际赋给该变量的对象决定 。 简称: 编译时看左边;运行时看右边 。
- 若编译时类型和运行时类型不一致就出现了对象的多态性 (Polymorphism).多态情况下看左边看的是父类的引用父类中不具备子类特有的方法看右边看的是子类的对象 实际运行的是子类重写父类的方法
对象的多态在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
1
2
3
4
5Person p = new Student();
Object o = new Person(); //Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
// 本人理解为,Person p 即为我需要一个 Person,叫 p ,new Student() 可以理解为你给我了一个学生,如果给我一个工人也可以,只要是属于 Person 类的(此为继承性)都可以。
// 因为任何类的根父类都是 object,所以我需要一个 object 类,你只要给我一个类就行,我不关心是什么类
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
- 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
1
2
3
4Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量 Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
// 属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。 - 多态的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.fun(new Dog());
test.fun(new Cat());
}
// Animal animal = new Dog();
// Animal animal = new Cat();
public void fun(Animal animal) {
animal.eat();
animal.shout();
}
}
class Animal {
public void eat() {
System.out.println("The animal are eating!");
}
public void shout() {
System.out.println("The animals are roaring!");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("The dog is eating the bone!");
}
public void shout() {
System.out.println("The dog is barking!");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("The cat is eating the fish!");
}
public void shout() {
System.out.println("The cat is called!");
}
}点击查看运行结果
1
2
3
4
5
6
7D:\PATH-EN\java-path\bin\java.exe ...
The dog is eating the bone!
The dog is barking!
The cat is eating the fish!
The cat is called!
Process finished with exit code 0
虚拟方法调用(Virtual Method Invocation)
- 正常的方法调用
1
2
3Person e = new Person();
e.getInfo();
Student e = new Student(); e.getInfo(); - 虚拟方法调用(多态情况下):子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
1
2Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法 - 编译时类型和运行时类型:编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
- 正常的方法调用
多态小结
- 多态作用:提高了代码的通用性,常称作接口重用
- 前提:1.需要存在继承或者实现关系 2.有方法的重写
- 成员方法:1.编译时:要查看引用变量所声明的类中是否有所调用的方法。2.运行时:调用实际new的对象所属的类中的重写方法。
- 成员变量:不具备多态性,只看引用变量所声明的类
instanceof 操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- 如果x属于类A的子类B,x instanceof A值也为true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Person extends Object {
…
}
public class Student extends Person {
…
}
public class Graduate extends Person {
…
}
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
}
对象类型转换 (Casting )
基本数据类型的Casting:
- 自动类型转换:小的数据类型可以自动转换成大的数据类型如:long g = 20; double d = 12.0f
- 强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型如: float f=(float)12.0; int a=(int)1200L
对Java对象的*强制类型转换称为造型
- 从子类到父类的类型转换可以自动进行(即为多态)
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
强转以及instanceof的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class PersonTest {
public static void main(String[] args) {
Person m = new Man();
/* 编译的时候 m 指的是 Person,解释的时候 m 指的是 Man
m.eat() 指的是 Man 中重写的 Person 中的 eat(),
如果 Man 中没有重写 eat() ,则 m.eat()值得是Person中的 eat()
*/
m.eat();
/* m.isMan(),m 只能调用 Person 中的属性方法,Man 为 Person 的子类,
Person 的 m 不可调用 Man 的方法,想要调用,可以理解为强转,将 Person 的 m 强转为 Man 的 m
就可以调用 Man 中独有的方法
注意:只有 上到下 的方式,没有 Man 转为 Woman 的方式
下到上就是多态
*/
// m.isMan();
if (m instanceof Person) {
((Man) m).isMan();
System.out.println("强转Person成功");
}
if (m instanceof Woman) {
System.out.println("强转Woman成功");
}
}
}
class Person {
String name;
public void eat() {
System.out.println("可以吃饭");
}
}
class Man extends Person {
public void eat() {
System.out.println("男人可以吃饭");
}
public void isMan() {
System.out.println("男人可以进入男厕");
}
}
class Woman extends Person {
public void isWoman() {
System.out.println("女人可以进入女厕");
}
}点击查看运行结果
1
2
3
4
5
6D:\PATH-EN\java-path\bin\java.exe ...
男人可以吃饭
男人可以进入男厕
强转Person成功
Process finished with exit code 0多态体会属性和方法的不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);
s.display();
Base b = s;
System.out.println(b == s);
System.out.println(b.count);
b.display();
}
}
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);
}
}点击查看运行结果
1
2
3
4
5
6
7
8D:\PATH-EN\java-path\bin\java.exe ...
20
20
true
10
20
Process finished with exit code 0子类继承父类
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。(编译看左边,运行看右边)
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的 实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量(编译运行都看左边)
Object类的使用
- Object类是所有Java类的根父类
- 如果在类的声明中未使用 extends 关键字指明其父类,则默认父类为 java.lang.Object类
1
2
3
4
5
6
7public class Person {
...
}
等价于:
public class Person extends Object {
...
}
==操作符与equals方法
- 基本类型比较值:只要两个变量的值相等,即为true。 eg: int a=5; if(a==6){…}
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,== 才返回true。
- 用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
- equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。① 只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。②格式:obj1.equals(obj2)
- 特例:当用equals() 方法进行比较时, 对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
- 当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等
重写equals()方法的原则
- 对称性: 如果x.equals(y)返回是“ true ”, 那么y.equals(x) 也应该返回是“true”。
- 自反性:x.equals(x)必须返回是“true”。
- 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
- 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你 重复x.equals(y)多少次,返回都是“true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
子类重写父类equals()示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39import java.util.Objects;
public class PersonTest {
public static void main(String[] args) {
Person person1 = new Person("张三", 18);
Person person2 = new Person("张三", 18);
System.out.println(person1 == person2);
System.out.println(person1.equals(person2));
}
}
class Person {
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 没有重写equals()的话,person1.equals(person2) 结果就是false
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return this.age == ((Person) o).age && Objects.equals(this.name, ((Person) o).name);
}
}点击查看运行结果
1
2
3
4
5D:\PATH-EN\java-path\bin\java.exe ...
false
true
Process finished with exit code 0简单理解为:== 和 equals(),前者可以比较基本和引用数据类型,后者因为是方法,所以只能比较引用数据类型,正常情况下 == 和 equals() 比较引用数据类型时候都是比较实例的地址值,因为所有的类的根父类都是 object,正常情况下都是调用根父类的equals(),对于根父类来说 equals() 就是比较地址值。但在实际情况中,多数情况下比较是否相等都是比较两个实例的内容是否相等,不是比较地址值,所以,此时就会有子类重写父类的 equals() 方法,使其比较两个实例的类型。
注意:String 类型的 equals(),也是重写过的,只不过不用我们自己来重写,直接 java 就设计了。
- toString() 方法
- toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
- 在进行String与其它类型数据的连接操作时,自动调用toString()方法
- Date now = new Date();System.out.println(“now=”+now);
- 相当于System.out.println(“now=”+now.toString());
- 可以根据需要在用户自定义类型中重写toString()方法 如String 类重写了toString()方法,返回字符串的值。
- s1=“hello”;
- System.out.println(s1);//相当于System.out.println(s1.toString());
- 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
- int a=10; System.out.println(“a=”+a);
面试题
1 | public class Test { |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
- Java中的JUnit单元测试
步骤:
- 安装JUnit插件步骤:File—>settings—>Plguins—>Browse repositories—>输入JUnit—>选择 JUnit Generator V2.0安装。
- 点击想要测试的类,快捷键:Alt + Shift + 0,出现候选框后点击 JUnit Test —>点击 JUnit 4 即可生成测试类
特点:
- 此类是public的
- 此类提供公共的无参的构造器
- 此时的单元测试方法:方法的权限是public,没有返回值,没有形参
- 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
声明好单元测试方法以后,就可以在方法体内测试相关的代码。
包装类(Wrapper)的使用
- 引入包装类的目的
针对八种基本数据类型定义相应的引用类型—包装类(封装类)
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象!
基本数据类型 <====> 包装类
基本数据类型包装成包装类的实例 —-> 装箱
- 通过包装类的构造器实现:int i = 500; Integer t = new Integer(i);
- 还可以通过字符串参数构造包装类对象:
- Float f = new Float(“4.56”);
- Long l = new Long(“asdf”); //NumberFormatException
获得包装类对象中包装的基本类型变量 —-> 拆箱
- 调用包装类的.xxxValue()方法:boolean b = bObj.booleanValue();
JDK1.5之后,支持自动装箱,自动拆箱。但类型必须匹配。
- 调用包装类的.xxxValue()方法:boolean b = bObj.booleanValue();
基本数据类型 <====> String类
字符串转换成基本数据类型
- 通过包装类的构造器实现:int i = new Integer(“12”);
- 通过包装类的parseXxx(String s)静态方法: Float f = Float.parseFloat(“12.1”);
基本数据类型转换成字符串
- 调用字符串重载的valueOf()方法:String fstr = String.valueOf(2.34f);
- 更直接的方式:String intStr = 5 + “”
基本类型、包装类与String类间的转换
示例
1 | import org.junit.Test; |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
小面试题
1 | // 输出结果是什么? |
解释:
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
第七章 面向对象编程(下)
关键字:static
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下, 某些特定的数据在内存空间里只有一份 ,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量 。
类属性、类方法的设计思想
- 类属性作为该类各个对象之间共享的变量。在设计类时分析哪些属性不因对象的不同而改变 ,将这些属性设置为类属性。相应的方法设置为类方法。
- 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法 ,从而简化了方法的调用。
使用范围:
在 Java 类中可用 static 修饰属性 、方法 、代码块 、内部类被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
类方法(class method)
- 没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类方法。
- 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
- 因为不需要实例就可以访问static方法,因此static方法内部不能有this。(也不能有super)
- static修饰的方法不能被重写
关于static的练习
1 | import org.junit.Test; |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
单例 (Singleton)设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。设计模型免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用 new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
单例设计的两种实现方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
class Bank {
// 1.私有化类的空参构造器
private Bank() {
}
// 2.在类的内部创建一个对象,要求这个对象是静态的," 3. "的静态方法只能访问静态成员变量
private static Bank instance = new Bank();
// 3.在类的内部提供一个公共的且是静态的方法返回一个实例
public static Bank getInstance() {
return instance;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
class Bank {
// 1.私有化类的空参构造器
private Bank() {
}
// 2.声明此对象,没有初始化," 3. "的静态方法只能访问静态成员变量
private static Bank instance = null;
// 3.声明 public static 返回当前类对象的方法
public static Bank getInstance() {
if (instance == null) {
instance = new Bank();
}
return instance;
}
}懒汉式暂时还存在线程安全问题,可修复
点击查看运行结果
1
2
3
4D:\PATH-EN\java-path\bin\java.exe ...
true
Process finished with exit code 0Java源码Runtime方法使用单例设计模式
- 单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。 - 单例模式-应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志 文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库 资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程 中,回收站一直维护着仅有的一个实例。
理解main方法的语法
由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是 public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须 是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令 时传递给所运行的类的参数。
又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
类的成员之四:代码块
- 代码块(或初始化块)的作用:
对Java类或对象进行初始化 - 代码块(或初始化块)的分类:
一个类中代码块若有修饰符, 则只能被static修饰,称为静态代码块(static block),没有使用static修饰的,为非静态代码块。
static代码块通常用于初始化static的属性 - 静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次
- 非静态代码块:没有static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
代码块的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public class BlockCodeTest {
public static void main(String[] args) {
// 1.static attribute and static functions are along with class loading(include static code block )
// 2.The functions only loading not to run
// 4.The static code block will run with class loading
// 5.Run it just once
// 6.Run from top to bottom if you have many of static code block
Test.fun();
// 1.The non-static code block will run with when you build a object
Test test = new Test();
System.out.println(Test.getName());
// 2.Every time when you build a object
// 3.Run from top to bottom if you have many of not-static code block
Test test1 = new Test();
System.out.println(test.age);
System.out.println(test1.age);
}
}
class Test {
private static String name;
int age;
public static String getName() {
return name;
}
public static void setName(String name) {
Test.name = name;
}
static {
System.out.println("I will output this sentence when you use static function or static variable");
}
{
name = "Jermyn";
age = 18;
System.out.println("I will output this sentence when you bulid a object because i am non-static code block");
}
public static void fun() {
System.out.println("static function");
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10D:\PATH-EN\java-path\bin\java.exe ...
I will output this sentence when you use static function or static variable
static function
I will output this sentence when you bulid a object because i am non-static code block
Jermyn
I will output this sentence when you bulid a object because i am non-static code block
18
18
Process finished with exit code 0
- 程序中成员变量赋值的执行顺序
- 声明成员变量的默认初始化
- 显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)
- 构造器再对成员进行初始化操作
- 通过”对象.属性”或”对象.方法”的方式,可多次给属性赋值
程序中成员变量赋值的执行顺序的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public class LeafTest {
public static void main(String[] args) {
new Leaf();
System.out.println("---------------------------分割线---------------------------");
new Leaf();
}
}
class Root {
static {
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root() {
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root {
static {
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid() {
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("Jermyn");
System.out.println("Leaf的构造器");
}
}点击查看运行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:Jermyn
Leaf的普通初始化块
Leaf的构造器
---------------------------分割线---------------------------
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:Jermyn
Leaf的普通初始化块
Leaf的构造器
Process finished with exit code 0总结:由父及子 静态先行
关键字:final
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
- final标记的类不能被继承。提高安全性,提高程序的可读性。
- String类、System类、StringBuffer类
- final标记的方法不能被子类重写。
- 比如:Object类中的getClass()。
- final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
- final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。
- final double MY_PI = 3.14;
1 | final class A{ |
1 | class A { |
1 | class A { |
尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
1 | public class Test { |
小结:
public static void main (String[] args){
方法体
}
权限修饰符:private, 缺省 , protected , public ——————>封装性
修饰符: static , final , abstract , native
返回值类型:无返回值, 有返回值的 return …;
方法名:需要满足表示符命名规则、规范:“见名知意”
形参列表:重载 VS 重写:参数的值传递机制,体现对象的多态性
方法体:体现方法的功能
抽象类与抽象方法
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
- 特点
- 用abstract关键字来修饰一个类,这个类叫做抽象类。
- 用abstract来修饰一个方法,该方法叫做抽象方法。
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:public abstract void talk();
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
- 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法(原因:私有的方法就不可被重写)、静态方法(原因:abstract修饰的类,就不可以创建类了,只可以创建类的子类)、final的方法(原因:final修饰的方法就不可被重写)、final的类(原因:final修饰的类就不可被继承)。
- 抽象类应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
在航运公司系统中,Vehicle类 需要定义两个方法分别计算运 输工具的燃料效率和行驶距离
问题:卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法但子类可以。- 解决方案:Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。
- Vehicle是一个抽象类,有两个抽象方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public abstract class Vehicle {
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) {
//写出计算卡车的燃料效率的具体方法
}
public double calcTripDistance( ) {
//写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vehicle {
public double calcFuelEfficiency( ) {
//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance( ) {
//写出计算驳船行驶距离的具体方法
}
}
抽象类的示例
1 | public class EmployeeTest { |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
- 多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象 类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以 把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用, 这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽 象出来,供不同子类实现。这就是一种模板模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
excuteCode();
long end = System.currentTimeMillis();
System.out.println("执行时间是:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
public void excuteCode() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
接口(interface)
概述
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打 印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则 必须能…”的思想。继承是一个”是不是”的关系,而接口实现则是 “能不能” 的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口的特点:
接口(interface)是抽象方法和常量值定义的集合。- 用 interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制。
接口的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// JDK7及以前:只能定义全局常量和抽象方法
// 全局常量:public static final的.但是书写时,可以省略不写
// 抽象方法:public abstract的
// 1.
public interface Runner {
int ID = 1;
void start();
public void run();
void stop();
}
// 2.
public interface Runner {
public static final int ID = 1;
public abstract void start();
public abstract void run();
public abstract void stop();
}
// 两种定义方法效果一样,均可注意
定义Java类的语法格式:先写 extends,后写 implements
- class SubClass extends SuperClass implements InterfaceA{ }
一个类可以实现多个接口,接口也可以继承其它接口。
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义 (JDK7.0及之前),而没有变量和方法的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// interface 示例
interface Runner {
public void start();
public void run();
public void stop();
}
class Person implements Runner {
public void start() {
// 准备工作
}
public void run() {
// 准备工作
}
public void stop() {
// 准备工作
}
}
*接口的应用:代理模式(Proxy)
概述:代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.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 work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
public void browse() {
check();
work.browse();
}
}应用场景:
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
接口和抽象类之间的对比
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。Java 8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中 找到像Collection/Collections或者Path/Paths这样成对的接口和类。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。 我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
接口中的默认方法
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。
- 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突,即为必须重写重写。
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
类的内部成员之五:内部类
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。Inner class的名字不能与包含它的外部类类名相同;
- 分类:①成员内部类(static成员内部类和非static成员内部类)②局部内部类(不谈修饰符)、匿名内部类
成员内部类作为类的成员的角色:
- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
成员内部类作为类的角色:
- 可以在内部定义属性、方法、构造器等结构
- 可以声明为 abstract 类 ,因此可以被其它的内部类继承
- 可以声明为 final 的
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
【注意】
非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
外部类访问成员内部类的成员,需要内部类.成员 或 内部类对象.成员的方式
成员内部类可以直接使用外部类的所有成员,包括私有的数据(此处可以当作方法理解)
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
- 如何声明局部内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class 外部类 {
// 方法
方法() {
class 局部内部类 {
}
}
// 代码块
{
class 局部内部类{
}
}
} 如何使用局部内部类
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
- 但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
局部内部类的特点
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。(例:OuterClass$InnerClass.class)
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。(此处可以当作方法理解)
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局 部变量的声明周期不同所致。
- 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
- 局部内部类不能使用static修饰,因此也不能包含静态成员
- 匿名内部类
- 匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 格式:
1
2
3new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}s1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28interface Product {
public double getPrice();
public String getName();
}
public class AnonymousTest {
public void test(Product p) {
System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
}
public static void main(String[] args) {
AnonymousTest ta = new AnonymousTest();
//调用test方法时,需要传入一个Product参数,
//此处传入其匿名实现类的实例
ta.test(new Product() {
public double getPrice() {
return 567.8;
}
public String getName() {
return "AGP显卡";
}
});
}
}点击查看运行结果
1
2
3
4D:\PATH-EN\java-path\bin\java.exe ...
购买了一个AGP显卡,花掉了567.8
进程已结束,退出代码0 - 匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
abstract类与接口的比较
(1)abstract类和接口都可以有abstract方法。
(2)接口中只能有常量,不能有变量;而abstract类中既可以有常量,也可以有变量。
(3)abstract类中也可以有非abstract方法(不是default方法,还带有方法体的方法)但不可以有default实例方法。接口不可以有非abstraet方法,但可以有default实例方法。在设计程序时应当根据具体的分析来确定是使用抽象类还是接口。abstract类除了提供重要的需要子类去实现的abstract方法外,还提供了子类可以继承的变量和非abstract方法如果某个问题需要使用继承才能更好地解决,例如,子类除了需要实现父类的abstract方法还需要从父类继承一些变量或继承一些重要的非abstraet方法,就可以考虑用abstract类。如果某个问题不需要继承,只是需要若干个类给出某些重要的abstract方法的实现细节,就可以考虑使用接口。
第八章 异常处理
异常概述与异常体系结构
异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源 耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性 的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使 用针对性的代码进行处理。例如:空指针访问;试图读取不存在的文件;网络连接中断;数组角标越界
解决办法
- 对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
- 捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等
异常体系结构
- 运行时异常unchecked
- 是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
- 对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
- 编译时异常checked
- 是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一 般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。
- 对于这类异常,如果程序不处理,可能会带来意想不到的结果。
- 运行时异常unchecked
常见异常
java.lang.RuntimeException
- ClassCastException
- ArrayIndexOutOfBoundsException
- NullPointerException
- ArithmeticException
- NumberFormatException
- InputMismatchException
- …….
java.io.IOExeption
- FileNotFoundException
- EOFException
java.lang.ClassNotFoundException
- java.lang.InterruptedException
- java.io.FileNotFoundException
- java.sql.SQLException
常见异常复现
1 | import org.junit.Test; |
异常处理机制一:try-catch-finally
- 注意:
- Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
- Java提供的是异常处理的抓抛模型。
- Java程序的执行过程中如出现异常,会生成一个异常类对象, 该异常对象将被提交给Java运行时系统,这个过程称为抛出 (throw)异常。
异常对象的生成
- 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出
- 由开发人员手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样
如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。 这一过程称为捕获(catch)异常。
- 如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
- 程序员通常只能处理Exception,而对Error无能为力。
- 语法格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15try{
......
//可能产生异常的代码
}catch( ExceptionName1 e ){
......
//当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
......
//当产生ExceptionName2型异常时的处置措施
}
[ finally{
......
//无论是否发生异常,都无条件执行的语句
} ] - try{}
捕获异常的第一步是用 try{…} 语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。 - catch (Exceptiontype e)
在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。 - finally{…}
- 捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够 对程序的状态作统一的管理。
- 不论在try代码块中是否发生了异常事件,catch语句是否执 行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。
- finally语句和catch语句是任选的
- 捕获异常的有关信息:
与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。- getMessage() 获取异常信息,返回字符串
- printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
注意:
如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。
比如 : 可以用 ArithmeticException 类作参数的地方 , 就可以用 RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。 但不能是与ArithmeticException类无关的异常,如NullPointerException(catch 中的>语句将不会执行)。
try…catch…finally示例
1 | import org.junit.Test; |
点击查看运行结果
1 | D:\PATH-EN\java-path\bin\java.exe ... |
异常处理机制二:throws
声明抛出异常是Java中处理异常的第二种方式
- 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这 种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理, 而由该方法的调用者负责处理。
- 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可 以是方法中产生的异常类型,也可以是它的父类。
声明抛出异常举例:
1
2
3
4
5
6public void readFile(String file) throws FileNotFoundException {
.....
// 读文件的操作可能产生FileNotFoundException类型的异常
FileInputStream fis = new FileInputStream(file);
.....
}重写方法声明抛出异常的原则:重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下, 对methodA()方法的调用-异常的捕获按父类声明的异常处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class A {
public void methodA() throws IOException {
……
}
}
public class B1 extends A {
public void methodA() throws FileNotFoundException {
……
}
}
public class B2 extends A {
public void methodA() throws Exception {
……
}
}开发中如何选择使用try-catch-finally 还是使用throws?
4.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
4.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
手动抛出异常
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
- 首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运 行环境)。
1
2IOException e = new IOException();
throw e; - 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将 会产生语法错误:
1
throw new String("want to throw");
手动抛出异常示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class PersonTest {
public static void main(String[] args) {
try {
Person p = new Person();
p.regist(-5);
System.out.println(p.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Person {
private int id;
public void regist(int id) throws Exception {
if (id > 0) {
this.id = id;
} else {
throw new Exception("The number you enter cannot be negative");
}
}
public String toString() {
return "Person{" +
"id=" + id +
'}';
}
}点击查看运行结果
1
2
3
4
5
6D:\PATH-EN\java-path\bin\java.exe ...
Exception in thread "main" java.lang.Exception: The data you entered is illegal
at com.jermyn.test14.Person.regist(PersonTest.java:27)
at com.jermyn.test14.PersonTest.main(PersonTest.java:14)
进程已结束,退出代码1
用户自定义异常类用户自定义异常类
- 一般地,用户自定义异常类都是RuntimeException的子类。
- 自定义异常类通常需要编写几个重载的构造器。
- 自定义异常需要提供serialVersionUID
- 自定义的异常通过throw抛出。
- 自定义异常最重要的是异常类的名字,当异常出现时,可以根据
- 名字判断异常类型。
- 步骤:
- 继承于现有的异常结构:RuntimeException 、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
1
2
3
4
5
6
7
8
9
10
11
12public class MyException extends Exception{
static final long serialVersionUID = -33875169924229948L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}小练习体会执行的先后顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class ReturnExceptionDemo {
static void methodA() {
try {
System.out.println("进入方法A");
throw new RuntimeException("制造异常");
} finally {
System.out.println("用A方法的finally");
}
}
static void methodB() {
try {
System.out.println("进入方法B");
return;
} finally {
System.out.println("调用B方法的finally");
}
}
public static void main(String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());
}
methodB();
}
}点击查看运行结果
1
2
3
4
5
6
7
8D:\PATH-EN\java-path\bin\java.exe ...
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally
进程已结束,退出代码0编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。练习
对数据类型不一致(NumberFormatException) 、 缺 少 命 令 行 参 数
(ArrayIndexOutOfBoundsException、除0(ArithmeticException),及输入负数(EcDef 自定义的异常)进行异常处理。
提示:
1.在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能
2.在main()方法中使用异常处理语句进行异常处理。
3.在程序中,自定义对应输入负数的异常类(EcDef)。
4.运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”
5.Interger类的static方法parseInt(String s)将s转换成对应的int值。 如:int a=Interger.parseInt(“314”);//a=314;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31import java.util.Scanner;
public class EcmDef {
public static void main(String[] args) throws EcDefException {
try {
int num1 = Integer.parseInt(args[0]);
int num2 = Integer.parseInt(args[1]);
int reult = ecm(num1, num2);
System.out.println(reult);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (NumberFormatException e) {
System.out.println("数据类型不一致");
} catch (ArithmeticException e) {
System.out.println("除0异常");
} catch (EcDefException e) {
System.out.println(e.getMessage());
}
}
public static int ecm(int num1, int num2) throws EcDefException {
if (num1 < 0 || num2 < 0) {
throw new EcDefException("不可小于零");
}
return num1 / num2;
}
}1
2
3
4
5
6
7
8
9
10
11public class EcDefException extends Exception {
static final long serialVersionUID = -3387516124229948L;
public EcDefException() {
}
public EcDefException(String msg) {
super(msg);
}
}点击查看运行结果
直接运行
1
2
3
4D:\PATH-EN\java-path\bin\java.exe ...
缺少命令行参数
进程已结束,退出代码01
2
3
4D:\PATH-EN\java-path\bin\java.exe ...
数据类型不一致
进程已结束,退出代码01
2
3
4D:\PATH-EN\java-path\bin\java.exe ...
除0异常
进程已结束,退出代码01
2
3
4D:\PATH-EN\java-path\bin\java.exe ...
不可小于零
进程已结束,退出代码0