课程笔记
02_Java概述
JDK介绍[p10]
JDK(Java Development Kit):Java开发工具包
JRE(Java Runtime Environment):Java运行环境
JVM(Java Virtual Machine):Java虚拟机
- JDK = JRE+java的开发工具
- JRE= JVM+ java的核心类库
java入门[p17-p19]
- 代码的相关说明:
- public class Hello 表示Hello是一个类,是public共有类
- Hello{} 表示一个类的开始和结束
- public static void main(String[] args){} 表示一个主方法,即程序的入口
- main(){} 表示主方法的开始和结束
- System.out.println(“”); 表示输出内容
- 每个语句都以 ; 结束
- java源文件以.java为扩展名,源文件名必须和public类名相同
- java语言严格区分大小写
- 一个源文件中最多一个public类,其他类的个数不限
public calss Hello{
}
class Cat{
}
class Dog{
}
- 也可以将main方法写到非public类中,然后指定运行非public类,这样入口方法就是非public的main方法
public class Hello{
public static void main(String[] args){
System.out.println("Hello");
}
}
class Hi{
public static void main(String[] args){
System.out.println("Hi");
}
}
运行结果如下
转义字符[p20]
public class ChangeChar{
public static void main(String[] args){
// \t : 一个制表位,实现对齐功能
System.out.println("北京\t上海\t深圳");
// \n : 换行符
System.out.println("dog\npig\ncat");
// \\ : 输出一个\ \\\\:输出两个\
System.out.println("C\\R\\U\\D");
// \" : 输出一个"
System.out.println("\"好好学习java\"");
// \' : 输出一个'
System.out.println("\'天天向上\'");
// \r : 一个回车,光标回到第一个字用上海替换北京
System.out.println("北京人\r上海");//上海人
}
}
程序运行结果如下:
注释[p23-p25]
- 单行注释
//这是单行注释
- 多行注释
/*这是多行注释
这是多行注释
*/
- 文档注释
类和方法的注释,要以java doc的方式来写
/**
* 这是文档注释
**/
–如何生成对应的文档注释
打开cmd =》 javadoc -d 文件名 -xx -yy 源码名
路径详解[p27]
如下图,在D盘中,文件夹abc1内有一个文件夹abc100,文件夹abc2内有一个文件夹 abc200
从 abc1\abc100 访问 abc2\abc200 内的文件
相对路径:
绝对路径: d:\abc2\abc200\文件名
作业
03_变量[p35-p62]
- 变量表示内存中的一个存储区域
- 该区域有自己的名称[变量名]和类型[数据类型]
- 变量必须先声明,后使用
- 该作用域的值可以在同一类型范围内不断变化
- 变量在同一个作用域内不能重名
- 变量 = 数据类型 + 变量名 + 值 ,变量三要素
+号的使用[p39]
- 当左右两边都是数值型时,做加法运算
- 当左右两边一方为字符串,做拼接运算
- 运算顺序,从左到右
System.out.println(11 + 22);// 33
System.out.println("11" + 22);// 1122
System.out.println(11 + 22 + "hello");// 33hello
System.out.println("hello" + 11 + 22);// hello1122
数据类型[p40]
- java数据类型分为两大类:基本数据类型 ,引用数据类型
- 基本数据类型:数值型:{整数类型:(byte[1Byte],short[2Byte],int[4Byte],long[8Byte]),浮点(小数)类型:(float[4Byte],double[8Byte])}
字符型(char[2Byte])
布尔型(boolean[1Byte])
- 默认值:int 0; short 0; byte 0; long 0; float 0.0; double 0.0; char \u0000; boolean false; String null;
- 引用数据类型:类(class),接口(interface),数组([ ])
基本数据类型[p41-p51]
- 声明long型常量须在后面加"l"或"L"
long n1 = 100l;
long n2 = 100L;
- 声明float型常量须在后面加"f"或"F"
float n1 = 1.2f;
float n2 = 1.2F;
通常情况使用double类型,比float更精准
- 浮点型常量有两种表示方式
//十进制数形式
double n1 = 512.0;//512.0
double n2 = .512;// 0.512
//科学计数法形式
double n3 = 5.12e2;// 5.12*10的平方
double n4 = 5.12e-2;// 5.12/10的平方
- 浮点数陷阱
double num1 = 2.7;
double num2 = 8.1/3;// 2.6999999999999997
//在进行相等判断时要小心
if(num1 == num2){
System.out.println("相等");
}
//应该是两个数的差值的绝对值,在某个精度范围内判断
if(Math.abs(num1 - num2) < 0.000001){
System.out.println("差值非常小,到我的规定精度,认为相等...");
}
- 字符编码表
- ASCII: 一共128个字符,一个字节可以表示256个字符(8位),ASCII只用128个(7位),转化为二进制第一位统一是0
- Unicode: 固定大小的编码,使用两个字节来表示字符,汉字和字母都占用两个字节,这样很浪费空间
- utf-8: 大小可变的编码,字母使用1个字节,汉字使用3个字节
- gbk: 可以表示汉字,而且范围广,字母使用一个字节,汉字2个字节,汉字不如utf-8多
- big5: 繁体中文,港澳台
基本数据类型转换[p52-p59]
- 自动类型转换
精度小的数据类型自动转换为精度大的数据类型
- char =>> int =>> long =>> float =>> double
- byte =>> short =>> int >> long =>> float =>> double
- 自动类型转换注意事项
- 有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算
int n1 = 10;
float d1 = n1 + 1.1;//错误,这里的1.1是double,(n1 + 1.1)应该为bouble
double d1 = n1 + 1.1;//正确
float d1 = n1 + 1.1f;//正确,1.1f是float
- byte/short与char没有类型转换
- byte、short、char三者进行计算时会转换为int类型
- boolean类型不参与类型转换
- 强制类型转换细节
int n = (int)1.9;// n = 1 造成精度损失
int n1 = 2000;
byte n2 = (byte)n1;// n2 = -48 造成数据溢出
int x = (int)(10-2*3.3);// x = 3 int只对最近的数有效,小括号提升优先级
int m = 100;
char c1 = 100;
char c2 = m;// 错误
char c3 = (char)m;// 正确 c3 = 'd'
- 基本数据类型和String类型的转换
- 基本数据类型 =>> String
int n1 = 100;
float f1 = 1.1f;
short s1 = 10;
double d1 = 12.1;
boolean b1 = true;
char c1 = '我';
String s1 = n1 + "";
String s2 = f1 + "";
String s3 = d1 + "";
String s4 = b1 + "";
String s5 = c1 + "";
System.out.println(s1 +" " + s2 + " " + s3
+ " " + s4 + " " + s5);//100 1.1 10 12.1 true 我
- String =>> 基本数据类型
使用基本数据类型对应的包装类调用parseXX方法,得到基本数据类型
后面OOP讲对象和方法会详细
要确保String类型能够转成有效的数据,比如不能把"Hello"转换成整型
String s = "123";
int num1 = Integer.parseInt(s);
double num2 = Double.parseDouble(s);
float num3 = Float.parseFloat(s);
long num4 = Long.parseLong(s);
byte num5 = Byte.parseByte(s);
short num6 = Short.parseShort(s);
boolean num7 = Boolean.parseBoolean(s);
char num8 = s.charAt(0);//得到字符串s的第一个字符 num8 = 1
作业
04_运算符[p63-p103]
算术运算符[p64-p67]
- 注意示例
public class ArithmeticOperator{
//编写一个main方法
public static void main(String[] args){
// 除/,加减乘同理
System.out.println(10 / 4);//2
double d = 10 / 4;//自动把结果转换为double类型,精度提升
System.out.println(d);//2.0
System.out.println(10.0/4);//2.5
//short byte char运算时自动提升为int型
short s = 1;
s = s + 1;//错误,整型自动提升为int型
s += 1;//正确,等于s = (short)(s+1)
// % 取模,取余
//%的本质,看一个公式a % b = a - a / b * b
System.out.println(10 % 3);// 10 - 10 / 3 * 3 = 1
System.out.println(10 % -3);// 10 - 10 / (-3) * (-3) = 1
System.out.println(-10 % 3);// (-10) - (-10) / 3 * 3 = -1
System.out.println(-10 % -3);// (-10) - (-10) / (-3) * (-3) = -1
// 自增++,自减--同理
int i = 10;
i++;//自增等价于i = i +1 => i = 11
System.out.println(i);//11
++i;//自增等价于i = i +1 => i = 12
System.out.println(i);//12
int j = 10;
i = 10;
int k = j++;// k = j, j = j + 1
int n = ++i;// i = i + 1, n = i
System.out.println(k + " " + j);//10 11
System.out.println(n + " " + i);//11 11
}
}
- i = i++结果是多少?为什么?
public class ExerP65{
//编写一个main方法
public static void main(String[] args){
int i = 1;
i = i++;//规则使用临时变量(1) temp = i; (2) i = i + 1; (3) i = temp;
System.out.println(i);// 1
int j = 1;
j = ++j;//规则使用临时变量(1) j = j + 1; (2) temp = j; (3) j = temp;
System.out.println(j);// 2
}
}
逻辑运算符[p70-p75]
- && 和 &
短路与&&如果第一个条件为false,第二个不判断,效率高
逻辑与&两个条件都要判断,效率低
- || 和 |
短路或||如果第一个条件为true,第二个条件不判断,效率高
逻辑或|两个条件都要判断,效率低 - !
!叫取反,或者非运算;当条件为true时,结果为false,反之为true - ^
^叫逻辑异或,当条件1和条件2不同时,结果为true,反之为false - 练习题
boolean x = ture;
boolean y = false;
int num = 46;
if((num++ == 46) && (y = true)){//num++ == 46为true,y = true是把true赋值给y,为true
num++;//48
}
if((x = false) || (++num == 49)){//x = false是把false赋值给x,++num == 49为true
num++;//50
}
System.out.println(num);//50
赋值运算符[p76-p77]
- 运算顺序从右往左
- 赋值运算符的左边 只能是变量,右边可以是变量、表达式、常量值
int num = 20; int num2 = 1+2*3; int num3 = a; - 复合赋值运算符的等价
a += 1; 等价于 a = a + 1; 同-=, *=, -=, %= - 复合赋值运算符会进行强制类型转换
byte num = 1;
num += 2;//num = (byte)(num + 2);
num++;// num = (byte)(num + 1);
三元运算符[p78-p80]
- 基本语法
条件表达式 ? 表达式1 : 表达式2;
如果条件表达式为true,返回表达式1,反之返回表达式2 - 注意细节
表达式1和表达式2要为可以赋值给接收变量的类型
或者使用强制类型转换
int a = 10;
int b = 20;
int c = a > b ? 1.1 : 2.2;//错误,这里1.1和2.2为double类型,接收变量的类型为int
int c = a > b ? (int)1.1 : (int)2.2;//正确,将1.1和2.2强制转换为int类型
int d = a < b ? a : b;//正确 abd均为int类型
double f = a < b ? a : b;//正确 ab为int类型,f为double类型,double可以接收int
运算符优先级[p81]
标识符[p82-p84]
标识符规则[p82]
- 由26个英文字母大小写,0-9,_或$组成
- 不能以数字开头 int 3a = 1;
- 不可以使用关键字和保留字,但可以包含
- 严格区分大小写,长度不限 int totalNum = 19;
- 标识符不能包含空格 int a b = 1;
标识符规范[p84]
- 包名:多单词组成时,所有字母都小写:aaa.bbb.ccc //比如com.zs.laernjava
- 类名、接口名:多单词组成时,所有单词首字母大写//比如TankShootGame[大驼峰]
- 变量名、方法名:多单词组成时,第一个单词首字母小写,后续单词首字母大写//比如tankShootGame[小驼峰]
- 常量名:所有字母都大写,多单词时用下划线连接//比如定义一个税率 TAX_RATE
- 更加详细的规范看文档
位运算[p100-p101]
计算机运算时,都是以补码的方式来计算的,运算后用原码表示
- 原码 反码 补码
- 二进制的最高位是符号位:0表示正,1表示负
- 正数的原码 反码 补码都一样
- 负数的反码 = 符号位不变,其他位取反
- 负数的补码 = 反码 + 1 ,负数的反码 = 补码 - 1
- 0的反码补码都是0
- 位运算符
- 按位与& :两位全是1,结果为1,反之为0
- 按位或| :只要有一位是1,结果为1,反之为0
- 按位取反~ :0取1,1取0 所以 -n = ~n + 1
- 按位异或^ :一位为0一位为1,结果为1,反之为0
- 算术右移>> :低位溢出,符号位不变,并用符号位补溢出的高位,本质是n / 2 / 2
- 算数左移<< :符号位不变,低位补0,本质是 n * 2 * 2
- 无符号右移>>> :低位溢出,高位补0
键盘输入语句[p86]
- 在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
- 代码演示
//演示接收用户的输入
import java.util.Scanner;//1.引入 Scanner类所在的包
public class Input{
public static void main(String[] args){
//2.创建Scanner对象,new 创建一个对象,体会myScanner就是Scanner的对象
Scanner myScanner = new Scanner(System.in);
//3.接收用户输入
System.out.println("请输入名字");
String name = myScanner.next();//当程序执行到next方法时,会等待用户输入
System.out.println("请输入年龄");
int age = myScanner.nextInt();
System.out.println("请输入薪水");
double sal = myScanner.nextDouble();
System.out.println("人的信息如下");
System.out.println("名字:" + name + "年龄:" + age + "薪水:" + sal);
}
}
作业
05_程序结构控制[p104-p154]
顺序控制[p104]
程序自上而下逐行执行,没有跳转
对应的执行语句:
分支控制[p105-p121]
if-else分支结构[p105-p114]
- 单分支if
基本语法:
if(条件表达式){
执行代码块(可以有多个)
}
单分支对应的流程图:
- 双分支
基本语法:
if(条件表达式){
执行代码块1
}else{
执行代码块2
}
双分支对应的流程图:
- 多分支
基本语法:
if(条件表达式1){
执行代码块1
}else if(条件表达式2){
执行代码块2
}
……
else if(条件表达式n-1){
执行代码块n-1
}else{//可以没有else
执行代码块n
}
多分支对应的流程图:
- 嵌套分支
import java.util.Scanner;
public class Exer{
public static void main(String[] args){
//出票系统:根据淡旺季的月份和年龄,打印票价
//4-10月:旺季
// 成人(18-60):60
// 儿童(<18):半价
// 老人(>60):1/3
//其他月份:淡季
// 成人(18-60):40
// 其他:20
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入当前月份:");
int month = myScanner.nextInt();
if(month <= 12 && month >= 1){
System.out.println("请输入年龄:");
int age = myScanner.nextInt();
if(month <= 10 && month >= 4){
if(age <= 60 && age >= 18){
System.out.println("票价为60元");
}else if(age < 18 && age > 0){
System.out.println("票价为30元");
}else if(age > 60){
System.out.println("票价为20元");
}else{
System.out.println("年龄输入有误");
}
}else{
if(age <= 60 && age >= 18){
System.out.println("票价为40元");
}else if((age > 0 && age < 18) || age > 60){
System.out.println("票价为20元");
}else{
System.out.println("年龄输入有误");
}
}
}else{
System.out.println("请重新输入月份(1-12)");
}
}
}
switch分支结构[p115-p121]
- 基本语法:
switch(表达式){
case 1://当表达式的值为常量1
语句块1;
break;
case 2:
语句块2;
break;
……
case n:
语句块n;
break;
default://表达式的值不等于1到n,执行default
default语句块;
break;
}
-
swtich的流程图:
-
switch注意事项和细节
- 表达式数据类型,应和case后的常量类型一致,或者是可以自动转换可以互相比较的类型,比如输入的是字符,常量是int
- swtich表达式的返回值必须是:int,byte,short,char,enum(枚举),String
- case后的值必须是常量
- default是可选的,没有匹配的case时,执行default
- break语句用来跳出switch语句块,如果没有写,则继续执行至出现break,没有则一直到switch结尾
循环控制[p122-p154]
for循环[p122-p126]
- 基本语法:
for(循环变量初始化;循环条件;循环变量迭代){
循环操作(语句);
}
- for循环的流程图:
while循环[p127-p129]
- 基本语法:
循环变量初始化;
while(循环条件){
循环体(语句);
循环变量迭代;
}
- 对应的流程图:
do…while循环[p131-p133]
- 基本语法:
循环变量初始化;
do{
循环体(语句);
循环变量迭代;
}while(循环条件);//先执行,后判断,至少执行一次
- 对应的流程图:
多重循环控制[p134-p137]
- 练习题:
//统计3个班成绩情况,每个班有5名同学,求出各个班的平均分和所有班级的平均分,并统计及格人数[成绩从键盘输入]
import java.util.Scanner;
public class MulForExer{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);
int classes = 3;//班级数为3
int students = 5;//每个班的学生数为5
double sum = 0;//所有班级总分数
int count = 0;//统计及格人数
for(int i = 0; i < classes; i++){//循环从1班到3班
System.out.println("请输入" + (i+1) +"班同学的成绩");//提示键盘输入成绩
double classSum = 0;//每个班级的总分数
for(int j = 0; j < students; j++){//循环每个班级的学生从1到5
double scores = myScanner.nextDouble();//输入学生成绩
classSum += scores;//班级总分数为每次输入的学生成绩相加
if(scores > 60){//如果学生成绩及格
count++;//计数+1
}
}
System.out.println((i + 1) + "班的平均分为:" + (classSum/students));//班级平均分=班级总分数/班级学生数
sum += classSum;//所有班级总分数为每次循环的班级总分数相加
}
System.out.println("所有班级的平均分为:" + (sum/(students*classes)));//所有班级平均分=所有班级总分数/所有学生人数
System.out.println("及格人数为:" + count);//打印及格人数
}
}
//打印9*9乘法表
for(int i = 1; i < 10; i++){//第一个乘数,1-9
for(int j = 1; j <= i; j++){//第二个乘数,不超过第一个乘数
System.out.print(j + " * " + i + " = " + (i * j) + "\t");
}
System.out.println("");//换行
}
//打印空心金字塔
// * 1格 第一层,1个*,前面4个空
// * * 3格 第二层,第一个和最后一个*,前面3个空格
// * * 5格 第三层,第一个和最后一个*,前面2个空格
// * * 7格 第四层,第一个和最后一个*,前面1个空格
// ********* 9格 第五层,9个全部是*,前面没有空格
int totalLevel = 5;//总共5层
for(int i = 1; i <= totalLevel; i++){//层数循环1-5
for(int n = 1; n <= totalLevel - i; n++){//循环打印前面的空格,空格数=总层数-所在层数
System.out.print(" ");
}
for(int j = 1; j <= 2 * i - 1; j++){//循环打印空心金字塔,每层金字塔所占格数=2 * 所在层数 - 1
if(i < totalLevel && (j > 1 && j < 2 * i - 1)){//判断是否空心,条件:不是最后一层&&不是第一个和最后一个数
System.out.print(" ");
}else{
System.out.print("*");
}
}
System.out.println("");//每一层结束换行
}
//打印空心菱形
//分为两部分
// * 1格 第1层,1个*,前面3个空格
// * * 3格 第2层,第一个和最后一个*,前面2个空格,中间1个空格
// * * 5格 第3层,第一个和最后一个*,前面1个空格,中间3个空格
// * * 7格 第4层,第一个和最后一个*,前面0个空格,中间5个空格
// * * 5格 第1层,第一个和最后一个*,前面1个空格,中间3个空格
// * * 3格 第2层,第一个和最后一个*,前面2个空格,中间1个空格
// * 1格 第3层,一个*,前面3个空格
int totalLevel = 7;
if(totalLevel % 2 == 0){//判断层数奇偶,偶数+1
totallevel++;
}
for(int i = 1; i <= totalLevel / 2 + 1; i++){//上半部分总层数=总层数 / 2 + 1
for(int n = 1; n <= totalLevel / 2 + 1 - i; n++){//循环打印前面的空格,空格数=上半部分总层数-所在层数
System.out.print(" ");
}
for(int j = 1;j <= i * 2 - 1; j++){//循环打印空心菱形上半部分,每层所占格数=2 * 所在层数 - 1
if(j == 1 || j == i * 2 - 1){//判断是否是*,条件:最第一个||最后一个数
System.out.print("*");
}else{
System.out.print(" ");
}
}
System.out.println("");//每层结束换行
}
for(int i = totalLevel / 2; i >= 1; i--){//下半部分参考上半部分,循环变量初始化和循环条件正好相反
for(int n = totalLevel / 2 - i + 1; n >= 1; n--){
System.out.print(" ");
}
for(int j = i * 2 -1;j >= 1; j--){
if(j == 1 || j == i * 2 - 1){
System.out.print("*");
}else{
System.out.print(" ");
}
}
System.out.println("");
}
跳转控制语句-break[p138-p143]
- 基本介绍:
break语句用于终止某个语句块的执行,一般用于switch或者循环中 - 在while循环的流程图:
- 注意事项和细节
break语句出现在多层嵌套的语句块中,可以通过指定标签指明要终止的是哪一层语句块,实际开发中尽量不使用
label1:
for(int i = 0; i < 5; i++){
label2:
for(int j = 0; j < 5; j++){
if(j == 3){
break label1;//终止lable1循环
}
}
}
跳转控制语句-continue[p144-p146]
- 基本介绍
continue语句用于结束本次循环,继续执行下一次循环,也可以通过标签指明要跳过的是哪一层循环,和break的规则一样 - 在while循环的流程图
跳转控制语句-return[p147]
- 基本介绍
return使用在方法,表示跳出所在的方法,在讲解方法时会详细介绍
作业
06_数组、排序和查找[p156-p190]
数组[p157-p171]
- 数组介绍
数组可以存放多个同一类型的数据。数组是一种数据类型,是引用类型。 - 数组的动态初始化
//第一种动态分配方式:
double[] score = new double[5];
//第二种动态分配方式:
double[] score;//先声明,这时score是null
score = new double[5];//分配内存空间,可以存放数据
- 数组的静态初始化
int[] a = {1,2,3,4,5};//知道数组多少元素和具体值
- 数组的注意事项
- 数组是多个相同数据类型的组合,实现对这些数据的统一管理
- 数组中的元素可以是任意数据类型,包括基本数据类型和引用类型
- 数组创建后,如果没有赋值,有默认值//int 0;short 0;byte 0;long 0;float 0.0;double 0.0;char \u0000;boolean false;String null;
- 使用数组的步骤1)声明数组并开辟空间 2)给数组各个元素赋值 3)使用数组
- 数组的下标是从0开始的
- 数组的下标必须在指定范围内使用,否则报:下标越界异常
- 数组属于引用类型,数组型数据是对象(object)
- 数组赋值机制
- 基本数据类型赋值,赋值方式是值拷贝/值传递,数据互相不影响
int a = 10;
int b = a;
b = 20;
System.out.println(a);//10
System.out.println(b);//20
- 如图所示:
- 数组在默认情况下是引用传递,赋的是地址,赋值方式是地址拷贝,arr2的变化会影响arr1
int[] arr1 = {1,2,3};
int[] arr2 = arr1;
arr2[0] = 10;
for(int i = 0; i < arr1.length; i++){
System.out.print(arr1[i]);//arr1 打印结果是10 2 3
System.out.print(arr2[i]);//arr2 打印结果是10 2 3
}
- 如图所示:
- 数组拷贝(内容复制),将arr1每个元素拷贝给arr2,不影响地址
int[] arr1 = {1,2,3};
int[] arr2 = new int[arr1.length];//开辟一个新的数据空间,大小和arr1一样
//遍历arr1,把每个元素拷贝到arr2
for(int i = 0; i < arr1.length; i++){
arr2[i] = arr1[i];
}
arr2[0] = 10;//修改arr2的第一个元素
for(int i = 0; i < arr1.length; i++){
System.out.print(arr1[i]);//arr1 打印结果是1 2 3
System.out.print(arr2[i]);//arr2 打印结果是10 2 3
- 如图所示
排序[p172-p174]
- 排序的分类
- 内部排序:
指将需要处理的数据所有数据都加载到内部存储器中进行排序,包括(交换式排序法、选择排序法和插入式排序法) - 外部排序法:
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序,包括(合并排序法和直接合并排序法)
- 冒泡排序法
public class BubbleSort{
public static void main(String[] args) {
//使用冒泡排序,将数组元素从小到大排列
int[] arr = {12,54,38,67,22};
int bigger = 0;
//外部循环,5个元素循环4次
for(int i = 0; i < arr.length - 1; i++){
//内部循环,外部循环i次,内部循环次数减少i次
for(int j = 0; j < arr.length - 1 - i; j++){
//将大数后移
if(arr[j] > arr[j + 1]){
bigger = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = bigger;
}
}
//打印每轮外部循环的数组元素
System.out.println("\n第" + (i + 1) + "轮循环");
for(int j = 0; j < arr.length; j++){
System.out.print(arr[j] + " , ");
}
}
}
}
查找[p175]
在java中,我们常用的查找有两种:
- 顺序查找
//有张三,李四,王五三个人,键盘输入一个名字,用顺序查找,找到打印名字和对应下标
import java.util.Scanner;
public class SeqSearch{
public static void main(String[] args){
Scanner myScanner = new Scanner(System.in);
String[] name = {"张三","王五","李四"};
System.out.println("请输入你要查找的名字");
String findName = myScanner.next();
int index = -1;//小技巧,用index的值来判断是否找到
//遍历数组,逐一比较
for(int i = 0; i < name.length; i++){
if(findName.equals(name[i])){
System.out.println("你要找的是:" + name[i]);
System.out.println("对应的下标是:" + i);
//如果找到了,把i保存到index
index = i;
break;
}
}
if(index == -1){
System.out.println("抱歉,没有找到" + findName);
}
}
}
- 二分查找(二分法,放在算法讲解)
多维数组
以二维数组为例
- 二维数组的内存形式:
- 案例:动态初始化-列数不确定
//创建一个二维数组,并输出
//i = 0 : 1
//i = 1 : 2 2
//i = 2 : 3 3 3
// ...
public class TwoDemensionalArray{
public static void main(String[] args){
//创建一个新的二维数组,只确定一位数组的个数
int[][] arr = new int[10][];
for(int i = 0; i < arr.length; i++){//遍历arr每个一维数组
//给每个一位数组一个空间 new
arr[i] = new int[i + 1];
//遍历一维数组,并给一维数组的每个元素赋值
for(int j = 0; j < arr[i].length; j++){
arr[i][j] = i + 1;
}
}
//遍历输出arr
for(int i = 0; i < n; i++){
//遍历输出arr的每个一位数组
for(int j = 0; j < arr[i].length; j++){
System.out.print(arr[i][j] + " ");
}
System.out.println("");
}
}
}
作业
07_面向对象编程(OOP)基础[p192-p263]
类与对象
- 类是抽象的,概念的,代表一类事物,比如人类,猫类…即它是数据类型
- 对象是具体的,实际的,代表一个具体事物,即是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
- 类与对象关系示意图
- 对象在内存中的存在形式
Person person = new Person();
person.name = "张三";//字符串是引用类型
person.age = 22;//基本数据类型
person.height = 180;
person.hairStyle = "光头";
- 类和对象的内存分配机制
java内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Person person,数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
成员方法
方法的调用机制
Public class Method{
public void main(String[] args){
Person p1 = new Person();
int sum = p1.getSum(10, 20);
System.out.println("两数和=" + sum);
}
}
class Person{
public int getSum(int num1. int num2){
int sum = nu1 + num2;
return sum;
}
}
成员方法的定义
访问修饰符(public private protected default) 返回数据类型 方法名(形参列表){
方法体
语句;
(retur 返回值;)
}
- 形参列表:表示成员方法输入
- 返回数据类型:表示成员方法的输入,void没有返回值
- 方法主体:表示为了实现某些功能
- ruturn 语句不是必须的
方法的注意事项和使用细节
- 访问修饰符有四个,public,private,protected,default,默认访问default不写
- 一个方法只能有一个返回值 [返回多个值可返回一个数组]
- 返回类型可以为任意类型,包含基本数据类型和引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法体中最后执行语句必须为 return 值;而且要求返回值类型必须与 return的值类型一致或兼容
- 如果方法是void,可以没有return语句,或只写return;
- 方法名:遵循驼峰命名法,见名知意
- 形参列表:参数类型可以为任意类型,包含基本数据类型和引用类型
- 方法定义时的参数成为形式参数,简称形参;方法调用时传入的参数成为实际参数,简称实参;形参和实参个数和顺序必须一致,类型一致或兼容
- 方法体里不能定义方法
- 同一个类中,方法可以直接调用
class Person{
public void print(int n){
System.out.println("print()被调用 n = " + n);
}
public void playGame(){
print(10);//直接调用 print()方法
System.out.println("playGame()执行");
}
}
- 跨类调用方法,需要通过对象名调用,和访问修饰符相关, 后面细讲
class Person{
public void playGame(){
System.out.println("Person类中的playGame()被调用");
}
}
class Cat{
public void play(){
Person p = new Person();
p.playGame();
System.out.println("Cat类中的play()执行");
}
}
成员方法传参机制
基本数据类型的传参机制
- 形参变化不会影响实参
public class Method{
pbulic static void main(String[] args){
int num1 = 10;
int num2 = 20;
Person p1 = new Person();
p1.swap(num1.num2);
System.out.prinln("main方法:num1 = " + num1 + ",num2 = " + num2);
}
}
class Person{
public void swap(int num1, int num2){
System.out.prinln("交换前:num1 = " + num1 + ",num2 = " + num2);
int temp;
temp = num1;
num1 = num2;
num2 temp;
System.out.prinln("交换后:num1 = " + num1 + ",num2 = " + num2);
}
}
引用类型的传参机制
- 引用类型传递的是地址值,可以通过形参改变实参
- 传递数组
public class Method{
public static void main(Sting[] args){
int[] arr = {1, 2, 3};
Person p1 = new Person();
p1.test(arr);
System.out.println("main方法");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
class Person{
public void test(int[] arr){
a[0] = 100;
System.out.print("调用test方法");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
- 传递其他类的对象
public class Method{
public static void main(String[] args){
Person p = new Person();
p.age = 10;
p.name = "张三";
System.out.println("调用test前的p.age = " + p.age);
Cat c = new Cat();
c.test(p);
System.out.println("调用test后的p.age = " + p.age);
}
}
class Person{
int age;
String name;
}
class Cat{
public void test(Person p){
p.age = 100;
}
}
- 克隆对象
public class MethodEx2{
public static void main(String[] args) {
Person p = new Person();
p.age = 11;
p.name = "张三";
//创建tools
MyTools tools = new MyTools();
Person p2 = tools.copyPerson(p);
//到此p和p2是两个独立的Person的对象,属性相同
System.out.println("p的属性 age = " + p.age + ",name = " + p.name);//age = 11,name = 张三
System.out.println("p2的属性 age = " + p2.age + ",name = " + p2.name);//age = 11,name = 张三
}
}
//编写一个copyPerson方法,可以复制一个Person对象,返回复制的值。
//克隆对象,注意要求得到的新对象和原来的对象是两个独立的对象,只是他们属性相同
class Person{
String name;
int age;
}
class MyTools{
public Person copyPerson(Person p){
Person p2 = new Person();
p2.name = p.name;
p2.age = p.age;
return p2;
}
}
方法递归调用
- 递归就是方法自己调用自己,每次调用时传入不同的变量。有助于解决复杂问题
- 案例1:打印问题
public class Recursion{
public static void main(String[] args){
T t = new T();
t.test(4);
}
}
class T{
public void test(int n){
if(n > 2){
test(n - 1);
}
System.out.println("n = " + n);
}
}
- 案例2:阶乘问题
public class Recursion{
public static void main(String[] args){
T t = new T();
int res = t.factorial(4);
System.out.ptintln("res = " + res);
}
}
class T{
public int factorial(int n){
if(n == 1){
return 1;
}else{
return factorial(n - 1) * n;
}
}
}
- 递归重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会互相影响
- 如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflow Error
- 当一个方法执行完毕,或遇到return,就会返回,谁调用就将结果返回给谁,同时当方法执行完毕或返回时,该方法也就执行完毕,释放空间
方法重载
java中允许一个类中,多个同名方法的存在,但要求形参列表不一致
- 注意事项
- 方法名必须相同
- 形参列表必须不同(形参类型或个数或顺序)参数名无要求
- 返回类型无要求
可变参数
java中允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法,就可以通过可变参数实现
- 基本语法
//数据类型... 表示接收的是可变参数,比如:int... nums
访问修饰符 返回类型 方法名(数据类型... 形参名){
}
- 可变参数的注意事项和使用细节
- 可变参数的实参可以为0个或任意多个
- 可变参数的实参可以为数组
- 可变参数本质就是数组
- 可变参数可以和普通类型的参数一起放放在形参列表,但必须保证可变参数在最后
public void v(String s, int... n)
- 一个形参列表只能出现一个可变参数
作用域
面向对象中,变量作用域是非常重要的知识点
- 在java中,主要的变量就是属性(成语)和局部变量
- 局部变量一般指在成员方法中定义的变量,局部变量必须赋值后,才能使用,因为没有默认值
- 全局变量:也就是属性,可以不赋值直接使用,因为有默认值
- 作用域的注意事项和使用细节
- 属性和局部变量可以重名,访问时遵循就近原则
- 在同一个作用域中,局部变量不能重名
- 属性生命周期较长,伴随对象的创建而创建,便随对象的销毁而销毁。局部变量生命周期较短,伴随代码块的执行而创建,伴随代码块的结束而销毁。
- 全局变量作用域:可以被本类使用,或其他类使用(通过对象调用)
class P{
public void test(){
T t = new T();
t.name;
}
}
class T{
String name;
}
局部变量作用域:本类中定义局部变量的代码块使用,比如public void say(){int a = 10;}
a的作用域在say方法中
- 全局变量可以加修饰符,局部变量不能加修饰符
构造器/构造方法
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化
- 基本语法
[修饰符] 方法名(形参列表){
方法体;
}
- 构造器没有返回值
- 方法名必须和类名一样
- 参数列表和成员方法一样的规则
- 在创建对象时,系统会自动调用该类的构造器来完成对对象的初始化
- 一个类可以定义多个不同的构造器,即构造器重载
- 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器
- 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显示定义一下无参构造器,如 Person(){ } 重要!!
this关键字[p246]
java虚拟机会给每个对象分配this代表当前对象,哪个对象调用,this就代表哪个对象
- this的注意事项和使用细节
- this关键字可以用来访问本类的属性、方法、构造器
- this用于区分当前类的属性和局部变量
- 访问成员方法的语句:this.方法名(参数列表)
- 访问构造器语法:this(参数列表);注意只能在构造器中使用(只能在构造器中访问另一个构造器,必须放在第一条语句)
- this不能在类定义的外部使用,只能在类定义的方法中使用
作业
08_面向对象编程中级[p264-p360]
IDEA[p264-p272]
包 [p273-p278]
包的本质 实际上就是创建不同的文件夹/目录来保存类文件
- 包的三大作用
- 区分相同名字的类
- 当类有很多时,可以很好的管理类
- 控制访问范围
- 包基本语法
package com.wiz.pkg;
- package 关键字,表示打包
- com.wiz.pkg 表示包名
- 命名规则和规范
- 只能包含数字、字母、下划线、小圆点.,不能以数字开头,不能是关键字和保留字
- com.公司名.项目名.业务模块名
- 常用的包
java.lang. * //lang包是基本包,默认引入
java.util.* //util包,系统提供的工具包,工具类,使用Scanner
java.net.* //网络包,网络开发
java.awt.* //是做java的界面开发,GUI
访问修饰符[p279-p280]
面向对象编程-封装
-
封装介绍
封装(encapsulation)就是把抽象出的数据[ 属性 ]和对数据的操作[ 方法 ]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[ 方法 ],才能对数据进行操作 -
封装的实现步骤
- 将属性进行私有化 private 【不能直接修改属性】
- 提供一个公共(public)的set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){//Xxx表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
- 提供一个公共(public)的get方法,用于获取属性的值
public void getXxx(类型 参数名){//权限判断,Xxx某个属性
return xx;
}
- 将构造器和setXxx结合
把setXxx方法写在构造器内
public Person(String name, int age, double salary){
setName(name);
setAge(age);
setSalary(salary);
}
面向对象编程-继承
继承可以解决代码复用,当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可
- 继承的基本语法
class 子类 extends 父类{
}
继承的细节问题
- 子类继承了所有的属性和方法,但父类私有的属性和方法不能在子类直接访问,要通过父类公共的方法去访问
- 子类必须调用父类的构造器,来完成子类的初始化
- 如果父类有无参构造器,不管使用子类哪个构造器都会默认调用父类的无参构造器,如果父类没有无参构造器,那么要在子类的构造器中用super(参数列表)去指定调用父类的哪个有参构造器完成对父类的初始化工作,否则编译不会通过
- 如果希望指定调用父类的某个构造器,则显式的调用一下:super(参数列表)
- super在使用时,必须放在构造器的第一个语句
- super()和this()都只能放在构造器的第一个语句,因此不能共存一个构造器
- java所有类都是Object类的子类,Object类是所有类的父类
- 父类构造器的调用不限于直接父类!将一直往上追溯到Object类(顶级父类)
- 子类最多只能继承一个父类,父类可以有多个子类
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
继承的本质分析
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name);//王三
System.out.println(son.age);//错误,在父类Father里有age,但是private,不能直接访问
System.out.println(son.hobby);//旅游
}
}
class Grand {
public String name = "王一";
public String hobby = "旅游";
public int age = 60;
}
class Father extends Grand{
public String name = "王二";
private int age = 36;
}
class Son extends Father{
public String name = "王三";
}
super关键字
- 基本介绍
super代表父类的引用,用于访问父类的属性、方法、构造器 - 基本语法
- 访问父类的属性,但不能访问private属性
super.属性名;
- 访问父类的方法,但不能访问private方法
super.方法名(参数列表);
- 访问父类的构造器(前面讲过)
super.(参数列表);
最多有一句放在构造器的第一行
- super使用细节
当子类中有和父类中的成员(属性 和 方法)重名时,为了访问父类的成员(属性 和 方法),必须通过super,如果多个上级父类中都有同名的成员(属性 和 方法),使用super访问遵循就近原则,访问最近的上级父类的成员,非私有则返回,私有则报错
this和super的比较
方法重写/覆盖(override)
简单的说,方法重写就是子类有一个方法,和上级父类方法的名称、返回类型、参数列表一样,那么就说子类的这个方法重写了上级父类的方法
- 注意事项和使用细节
- 子类方法的名称和参数列表,要和父类方法的完全一样
- 子类方法的返回类型,要和父类方法的返回类型一样,或是父类方法返回类型的子类,比如
public object getInfo(){}//父类
public String getInfo(){}//子类
- 子类方法不能缩小父类的访问权限
方法重载和方法重写的比较
面向对象编程-多态
多态:方法或对象具有多种形态,建立在封装和继承之上
- 多态的体现
- 方法的多态
重载和重写就体现多态 - 对象的多态
一个对象的编译类型和运行类型可以不一致,编译类型不能改变,运行类型可以改变,可以通过getClass()方法查看对象的运行类型
Animal animal = new Cat();
//animal编译类型是Animal,运行类型是Cat,animal可以接收子类Cat的对象
animal = new Dog();
//animal编译类型不变,运行类型是Dog,animal可以接收子类Dog的对象
- 实例
public class Poly {
public static void main(String[] args) {
Cat cat = new Cat("小黄");
Dog dog = new Dog("大黄");
Fish fish = new Fish("鱼");
Bone bone = new Bone("骨头");
Master master = new Master("张三");
//运行类型是Cat和Fish,接收Cat和Fish的对象
System.out.println(master.feed(cat, fish));//主人张三在给小黄喂鱼
//运行类型是Dog和Bone,接收Doge和Bone的对象
System.out.println(master.feed(dog, bone));//主人张三在给大黄喂骨头
}
}
//主人类
class Master{
String name;//主人类的属性 名字
public Master(String name) {//有一个参数 name 的构造器
this.name = name;
}
//编写一个主人喂食的方法,可以接收动物类的子类的对象和食物类的子类的对象
//编译类型为Animal和Food,运行类型是Animal的子类和Food的子类
public String feed(Animal animal, Food food){
return "主人" + name + "在给" + animal.getName()
+ "喂" + food.getName();
}
}
//动物类
class Animal{
String name;//动物类的属性 名字
public Animal(String name) {//有一个参数 name 的构造器
this.name = name;
}
//setter && getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//动物类的子类,猫类
class Cat extends Animal{
public Cat(String name) {//猫类的构造器
super(name);//父类没有无参构造器,子类要调用一个父类的构造器
}
}
//动物类的子类,狗类
class Dog extends Animal{
public Dog(String name) {//狗类的构造器
super(name);//父类没有无参构造器,子类要调用一个父类的构造器
}
}
//食物类
class Food{
String name;//食物类的属性 名字
public Food(String name) {//有一个参数 name 的构造器
this.name = name;
}
//setter && getter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//食物类的子类,鱼类
class Fish extends Food{
public Fish(String name) {//鱼类的构造器
super(name);//父类没有无参构造器,子类要调用一个父类的构造器
}
}
//食物类的子类,骨头类
class Bone extends Food{
public Bone(String name) {//骨头类的构造器
super(name);//父类没有无参构造器,子类要调用一个父类的构造器
}
}
多态的注意事项和细节
- 多态的前提是:两个对象(类)存在继承关系
- 多态的向上转型:
1.本质:父类的引用指向了子类的对象
2.语法:父类类型 引用名 = new 子类类型(); 如Animal animal = new Cat();
3.特点:编译类型看左边,运行类型看右边
按查找关系,从子类开始查找,可以调用父类(编译类型)的所有属性和方法,不能调用子类(运行类型)的特有属性和方法 - 多态的向下转型:
1.语法:子类类型 引用名 = (子类类型)父类引用;如Cat cat = (Cat)animal
这里的animal是Animal animal = new Cat();
2.只能强转指向子类对象的父类的引用 如Animal nimal = new Cat();
,不能强转指向父类对象的父类的引用 如Animal animal = new Animal();
3.要求父类的引用 如Animal animal
必须指向子类的对象 如new Cat()
4.向下转型后,子类就可以调用子类中所有的属性和方法 - 属性没有重写之说,父子类中相同的属性根据编译类型访问,可以看继承的内存分析
- instanceof 比较操作符,用于判断对象的运行类型是否为Xxx类型或Xxx类型的子类型
public class Instanceof {
public static void main(String[] args) {
//编译类型为AA 运行类型为AA
AA aa = new AA();
//编译类型为BB 运行类型为BB
BB bb = new BB();
//编译类型为AA 运行类型为BB
AA ab = new BB();
//编译类型为String 运行类型为String
String str = "hello";
//编译类型为Object 运行类型为Object
Object obj = new Object();
System.out.println(aa instanceof AA);//true
System.out.println(bb instanceof BB);//true
System.out.println(ab instanceof AA);//true
System.out.println(ab instanceof BB);//true
System.out.println(obj instanceof AA);//false
System.out.println(str instanceof Object);//true
}
}
class AA {
}
class BB extends AA {
}
java的动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定,如果运行类型没有该方法,则根据继承机制查找
2.当调用对象属性的时候,没有动态绑定机制,哪里声明了属性,使用哪里的属性,如果没有声明,则根据继承机制查找
public class DynamicBinding {
public static void main(String[] args) {
//编译类型为A 运行类型为B
A a = new B();
System.out.println(a.sum());//21 sum()的返回值调用B类的getI()方法
System.out.println(a.sum1());//12 调用A类的sum1方法
//编译类型为A 运行类型为B
A a2 = new C();
System.out.println(a2.sum());//11 C类没有getI() 查找到父类A类的getI()
System.out.println(a2.sum1());//20 调用C类的sum1()方法
}
}
class A{
int i = 10;
//setter && getter
public int getI() {
return i;//这里的i是A惊雷声明的属性i = 10
}
public void setI(int i) {
this.i = i;
}
public int sum(){
return getI() + 1;//根据运行类型调用getI()方法
}
public int sum1(){
return i + 2;//这里的i是A类声明的属性i = 10
}
}
class B extends A{
int i = 20;
//setter && getter
public int getI() {//B类的getI()方法
return i;//这里的i是B类声明的属性i = 20
}
public void setI(int i) {
this.i = i;
}
}
class C extends A{
public int sum1(){//C类的sum1()方法
return i + 10;//这里的i查找到父类A类的属性i = 10
}
}
多态的应用
- 多态数组
数组的定义类型为父类类型,里面保存的元素类型为子类类型
应用实例:
public class PolyArray {
public static void main(String[] args) {
Person[] people = new Person[5];
//数组元素编译类型均为Person 运行类型不同
people[0] = new Person("张三", 21);
people[1] = new Student("李四", 11, 100);
people[2] = new Student("王五", 12, 60);
people[3] = new Teacher("马六", 33, 10000);
people[4] = new Teacher("牛二", 55, 20000);
for (int i = 0; i < people.length; i++) {
System.out.println(people[i].say());//调用人类方法
if (people[i] instanceof Student) {//判断运行类型
((Student)(people[i])).study();//向下转型调用Student类的特有方法
} else if (people[i] instanceof Teacher) {
((Teacher)(people[i])).teach();//向下转型调用Teacher类的特有方法
} else if (people[i] instanceof Person) {
System.out.println();
}else {
System.out.println("你的信类型有误,请检查...");
}
}
}
}
//人类
class Person {
private String name;
private int age;
//方法
public String say(){
return name + "\t" + age;
}
//构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//setter && getter
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;
}
}
//学生类 继承人类
class Student extends Person{
private double score;
//重写父类方法
@Override
public String say() {
return "学生 " + super.say() + " score = " + score;
}
//学生的特有方法
public void study(){
System.out.println("学生 " + getName() + " 正在学java...");
}
//构造器
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
//setter && getter
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
//老师类 继承人类
class Teacher extends Person {
private double salary;
//重写父类方法
@Override
public String say() {
return "老师 " + super.say() + " salary = " + salary;
}
//老师的特有方法
public void teach(){
System.out.println("老师 " + getName() + " 正在上课...");
}
//构造器
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
//setter && getter
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
- 多态参数
方法定义的形参类型为父类类型,实参类型为子类类型
实例1:主人喂食
实例2:
public class PolyParameter {
public static void main(String[] args) {
PolyParameter polyParameter = new PolyParameter();
Worker worker = new Worker("张三", 3000);
Manager manager = new Manager("李四", 5000, 20000);
polyParameter.showEmpAnnual(worker);
polyParameter.showEmpAnnual(manager);
polyParameter.testWork(worker);
polyParameter.testWork(manager);
}
//实现获取任何员工对象的年工资
public void showEmpAnnual(Employee employee) {
System.out.println(employee.getName() + " 的年工资是" + employee.getAnnual());
}
//如果是worker调用work方法,如果是manager调用manage方法
public void testWork(Employee employee) {
if (employee instanceof Worker) {
((Worker) employee).work();
} else if (employee instanceof Manager) {
((Manager) employee).manage();
}else {
System.out.println("无");
}
}
}
//员工类
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
//计算年薪方法
public double getAnnual() {
return 12 * salary;
}
//setter && getter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
//经理类 继承员工类
class Manager extends Employee {
private double bonus;//manager的特有属性
public Manager(String name, double salary, double bonus) {//manager的构造器
super(name, salary);
this.bonus = bonus;
}
//重写父类方法
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
//manager的特有方法
public void manage() {
System.out.println("经理 " + getName() + " 正在managing");
}
//setter && getter
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
//工人类 继承员工类
class Worker extends Employee {
public Worker(String name, double salary) {//worker的构造器
super(name, salary);
}
//重写父类方法
@Override
public double getAnnual() {
return super.getAnnual();
}
//worker的特有方法
public void work() {
System.out.println("工人 " + getName() + " 正在working");
}
}
Object类详解
equals()方法
-
equals是Object类中的方法,只能判断引用类型,默认判断的是地址/对象是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer,String…
-
==运算符和equals方法对比
-
重写equals练习
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("张三", 11, '男');
Person person2 = new Person("张三", 11, '男');
System.out.println(person1.equals(person2));//true
}
}
class Person {
public String name;
public int age;
public char gender;
//重写equals方法
//判断两个Person的内容是否相等,各个属性都一样返回true
public boolean equals(Object obj){
if (this == obj){//如果比较的两个对象是一个对象,直接返回true
return true;
}
if (obj instanceof Person){//是Person才比较
return ((Person) obj).name.equals(this.name) && ((Person) obj).age == this.age
&& ((Person) obj).gender == this.gender;//向下转型,比较对象的各个属性
}
//不是Person返回false
return false;
}
//构造器
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
//setter && getter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
}
hashCode()方法
- 返回该对象的哈希码值,提高哈希结构的容器的效率
- 两个引用指向同一个对象,则哈希值肯定是一样的
- 两个引用指向不同的对象,则哈希值是不一样的
- 不能将哈希值等价于地址
后面会讲重写hashCode()方法
toString()方法
- 默认返回:全类目 + @ + 哈希值的十六进制
- 子类往往会重写toString方法,用于返回对象的属性信息
- 当直接输出一个对象时,toString方法就会被默认的调用
Person person = new Person();
//下面两种输出等价
System.out.println(person);
System.out.println(person.toString());
- 重写toString()方法
public class EqualsExercise01 {
public static void main(String[] args) {
Person person = new Person("张三", 11, '男');
System.out.println(person);//Person{name='张三', age=11, gender=男}
}
}
class Person {
public String name;
public int age;
public char gender;
//重写toString方法,输出对象的属性
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
//构造器
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
//setter && getter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
}
finalize方法
- 当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些释放资源的操作
- 什么时候被回收?
当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法 - 垃圾回收机制的调用,是由系统来决定的(即有自己的GC算法),也可以通过System.gc()主动触发垃圾回收机制,但并不保证会确实进行垃圾回收,JVM的垃圾回收只收集哪些由new关键字创建的对象。所以,如果不是用new创建的对象,你可以使用finalize函数来执行清理
实际开发中,几乎不会使用finalize方法
断点调试[p328-p334]
断点调试过程中,是以对象的运行状态来执行的
必须掌握的技能,多练习
小项目-零钱通[p335-p342]
作业[p343-p359]
09_项目-房屋出租系统[p362-p372]
10_面向对象编程高级部分
类变量和类方法[p374-p382]
类变量
- 什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问和修改它时,访问和修改的都是同一个变量。 - 如何定义类变量
定义语法:
访问修饰符 static 数据类型 变量名;//推荐
static 访问修饰符 数据类型 变量名;
- 如何访问类变量
静态变量的访问修饰符的访问权限和范围和普通属性是一样的
类名.类变量名//推荐
对象名.类变量名
类变量的内存布局
- JDK7以上的版本,静态域存储于定义类型的class对象中,class对象如同堆中其他对象一样,存在GC(垃圾回收)堆中(java堆因为是垃圾收集器的主要管理对象,常被称为GC堆)
- JDK7以下的版本,静态变量存储于方法区中
对应代码:
public class StaticChild {
public static void main(String[] args) {
//即使没有创建对象实例也可以访问类变量
System.out.println("现在有 " + Child.count + " 个小孩");//现在有0个小孩
Child child1 = new Child("张三");
System.out.println("一个小孩加入 name = " + child1.getName());
child1.count++;//对象名.类变量名 1
Child child2 = new Child("李四");
System.out.println("一个小孩加入 name = " + child2.getName());
child2.count++;//对象名.类变量名 2
System.out.println("现在有 " + Child.count + " 个小孩");//类名.类对象名 //现在有2个小孩
}
}
class Child {
private String name;
//类变量
public static int count = 0;
//构造器
public Child(String name) {
this.name = name;
}
//setter && getter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
类变量的注意事项和使用细节
- 类变量在同类中所有对象共享
- 类变量,在类加载的时候就生成了,所以即使没有创建对象实例也可以访问
- 加上static才是类变量/静态变量,没有static是普通变量/普通属性/非静态变量
- 访问类变量前提是 满足访问修饰符的访问权限和范围
- 类变量的生命周期是随着类的加载开始,随着类的消亡而销毁
- 什么时候需要用类变量?
当我们需要让某个类的所有对象都共享一个变量时,比如:定义学生类,统计所有学生交的钱 Student(name, static fee)
类方法
- 类方法基本介绍
类方法也叫静态方法,是该类的所有对象共享的方法 - 如何去定义类方法
访问修饰符 static 方法名() { }//推荐
static 访问修饰符 方法名() { }
- 类方法的调用
满足访问修饰符的访问权限和范围
类名.方法名
对象名.方法名
- 类方法经典的使用场景
- 当方法不涉及任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率 比如:工具类中的方法
- 在程序实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了
类方法的注意事项和使用细节
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
- 类方法中不允许使用和对象有关的关键字,比如this,super
- 类方法(静态方法)只能访问静态成员[类变量(静态变量)和类方法(静态方法)],如果要访问非静态成员,必须创建该类的一个实例对象后,通过对象去访问非静态成员
- 普通成员方法(非静态方法)可以访问所有(静态和非静态)成员
- 访问类方法前提是 满足访问修饰符的访问权限和范围
理解main方法语法[p383-p385]
代码块[p386-p391]
- 基本介绍
代码化块又称初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来,只有方法体,在加载类或创建对象时隐式调用 - 基本语法
代码块分为两类,static修饰的叫静态代码块,没有static修饰的叫普通代码块
[static]{
代码
}
代码块的注意事项和使用细节
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而隐式调用,并且只执行一次。
- 类什么时候被加载?
1)创建对象实例时
2)创建子类对象实例时,父类也会被加载
3)使用静态成员(属性和方法)时,使用子类静态成员时,父类也会被加载
- 普通代码块,在创建对象实例时会被隐式调用,创建一个对象才会执行一次。
- 在创建一个对象时,在一个类 调用顺序是:
1)调用静态代码块和静态属性初始化(静态代码块和静态属性初始化调用的优先级一样,按他们定义的顺序调用)
2)调用普通代码块和普通属性初始化(按定义顺序调用)
3)调用构造方法
- 构造器最前面隐含了super()和调用普通代码块
- 有继承关系时,创建一个子类对象时,调用的顺序是:
1)父类的静态代码块和静态属性初始化(按定义顺序调用)
2)子类的静态代码块和静态属性初始化(按定义顺序调用)
3)父类的普通代码块和普通属性初始化(按定义顺序调用)
4)父类的构造方法
5)子类的普通代码块和普通属性初始化(按定义顺序调用)
6)子类的构造方法 - 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
final关键字[p394-p397]
- final可以修饰类、属性、方法和局部变量
- final修饰的 类 ,不能被继承,但是可以实例化对象
- final修饰的 属性,不能被修改,必须赋初值,赋值可以有三种(只能赋值一次):
1)可以在定义时赋值
2)可以在代码块中赋值(静态属性在静态代码块中)
3)可以在构造器中赋值(静态属性不可以) - 不在final类里用final修饰的 方法,不能被重写/覆盖,可以被继承
- final修饰的 局部变量,不能被修改
- final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
- 包装类(Integer、Double、Float、Boolean等)和String都是final类
抽象类[p398-p402]
- 抽象类的介绍
- 用abstract关键字修饰的一个类,就是抽象类,语法:
访问修饰符 abstract class 类名{ }
- 用abstract关键字修饰的一个方法,就是抽象方法,语法:
访问修饰符 abstract 返回类型 方法名(参数列表);
- 抽象类的价值更多作用于设计,是设计者设计好后,让子类继承并实现抽象类
- 抽象类的注意事项和使用细节
- 抽象类不能实例化对象
- 抽象类不一定包括抽象方法,但是有抽象方法一定是抽象类
- abstract只能修饰 类 和 方法(除构造方法和类方法)
- 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样
- 抽象方法不能有方法体
public abstract void say();
- 如果一个非抽象类继承了抽象类,则必须实现抽象类里所有的抽象方法
- 抽象方法不能使用 praivate 、final 和 static 来修饰,因为这些关键字都是与重写相违背的
接口
- 基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
接口语法:
interface 接口名{
//属性
//方法(1.抽象方法 2.默认实现方法 3.静态方法)
}
实现接口语法:
class 类名 implements 接口名{
//必须实现接口的抽象方法
}
接口的注意事项和使用细节
- 接口不能实例化对象
- 接口中所有的方法都是public方法,接口中抽象方法可以不用abstract修饰
- 一个普通类实现接口,就必须将该接口的所有方法都实现
- 抽象类实现接口,可以不实现接口的抽象方法
- 一个类同时可以实现多个接口
- 接口中的属性默认是public static final修饰的
- 接口不能继承其他类,但是可以继承多个接口
- 接口的修饰符 只能是 public 和 默认
- 接口中属性的访问可以通过
接口名.属性名; 类名.属性名; 实现接口的类的对象名.属性名;
实现接口vs继承类
- 接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现 - 接口比继承更加灵活
- 接口在一定程度上实现代码解耦【即:接口规范性+动态绑定】
接口的多态
接口的类型可以指向实现了接口的类的对象,类似对象多态
内部类
- 一个类的内部又完整的嵌套了另一个类结构,本质还是类。被嵌套的类成为内部类(inner class),嵌套其他类的类成为外部类(outer class)。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。类的五大成员:属性、方法、构造器、代码块、内部类
- 基本语法
class Outer { //外部类
class Inner { //内部类
}
}
class Other{ //外部其他类
}
内部类的分类
- 定义在局部位置(方法/代码块):局部内部类、匿名内部类
- 定义在成员位置:成员内部类、静态内部类
局部内部类
- 基本语法
class Outer {//外部类
public void say() {//外部类的方法
class 类名 { //局部内部类
...
}
}
{//外部类的代码块
class 类名 {//局部内部类
...
}
}
}
- 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
- 可以直接访问外部类的所有成员
- 不能添加访问修饰符,因为它的地位就是一个局部变量,可以用final修饰
- 作用域:在定义它的方法或代码块中
- 外部类访问局部内部类,通过在定义局部内部类的方法或代码块中创建对象,用创建的对象访问局部内部类的成员
- 外部其他类不能访问局部内部类
- 当外部类和局部内部类的成员重名时,遵循就近原则,如果想要访问外部类的成员,则可以使用
外部类名.this.成员
去访问,这里的外部类名.this
就是调用局部内部类所在方法的外部类对象
匿名内部类
- 基本语法
class Outer {//外部类
public void say() {//外部类的方法
new 类或接口(参数列表) {//匿名内部类
类体
};
}
{//外部类的代码块
new 类或接口(参数列表) {//匿名内部类
类体
};
}
}
- 匿名内部类相当于(实现了一个接口的一个类)的对象,或(继承了其他外部类的一个类)的对象,本质是类,编译类型是实现的接口或继承的类,运行类型是匿名内部类
// IA ia = new IA(){ }; 相当于,
// 实现了IA接口的XXXX类,立即创建对象,把对象的地址返回给ia
/*
class XXXX implements IA {
@Override
public void walk() {
System.out.println("匿名内部类walk()");
}
}
IA ia = new XXXX();
*/
IA ia = new IA() {
@Override
public void walk() {
System.out.println("匿名内部类walk()");
}
};
// Person person = new Person() { }; 相当于,
// 继承了Person的XXXX类,立即创建对象,把对象的地址返回给person
/*
class XXXX extends Person() {
@Override
public void say() {
System.out.println("匿名内部类say()");
}
}
*/
Person person = new Person() {
@Override
public void say() {
System.out.println("匿名内部类say()");
}
};
- 调用匿名内部类的方法有两种方式
//第一种: 匿名内部类.方法名(参数列表);
new Person("张三") {//相当于是继承了Person类的Outer类的对象
@Override
public void say() {
System.out.println("匿名内部类say()" + getName());
}
}.say();
//第二种:引用一个对象指向匿名内部类,用该对象调用
Person person = new Person("李四") {
@Override
public void say() {
System.out.println("匿名内部类say()" + getName());
}
};
person.say();
- 可以直接访问外部类的所有成员,包含私有,不同包的一个类的构造方法不是public的,可以使用匿名内部类 即根据构造fasuper()
- 不能添加访问修饰符,因为它的地位就是一个局部变量
- 作用域:定义它的方法或代码块中
- 外部其他类不能访问局部内部类
- 当外部类和匿名内部类的成员重名时,遵循就近原则,如果想要访问外部类的成员,则可以使用
外部类名.this.成员
去访问,这里的外部类名.this
就是调用匿名内部类所在方法的外部类对象
成员内部类
- 成员内部类定义在外部类的成员位置,并没有static修饰
- 可以直接访问外部类的所有成员,包含私有的
- 可以添加任意访问修饰符
- 作用域:外部类内
- 外部类访问成员内部类,先创建成员内部类对象再访问
- 外部其它类访问成员内部类
Outer outer = new Outer();
//方法1,通过外部类对象访问
Outer.Inner inner = outer.new Inner();
//方法2,在外部类中,编写一个方法,可以返回成员内部类对象
Outer.Inner innerInstance = outer.getInnerInsetance();
- 当外部类和成员内部类的成员重名时,遵循就近原则,如果想要访问外部类的成员,则可以使用
外部类名.this.成员
去访问,这里的外部类名.this
就是成员内部类所在的外部类对象
静态内部类
静态内部类是定义在外部类的乘员位置,并且有static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,不能访问非静态成员
- 可以添加任意访问修饰符
- 作用域:外部类内
- 外部类访问静态内部类,先创建静态内部类对象再访问
- 外部其他类访问静态内部类
//方法一,通过类名直接访问
Outer.Inner inner = new Outer.Inner();
//方法二,在外部类,编写一个方法,可以返回静态内部类对象
Outer.Inner inner = Outer.getInner();
- 当外部类和静态内部类的成员重名时,遵循就近原则,如果想要访问外部类的成员,则可以使用
外部类名.成员
去访问,因为这里的成员是静态的
11_枚举和注解
枚举
自定义枚举类
enum关键字实现枚举注意事项
- 当我们使用enum关键字开发一个枚举类时,默认会继承Enum类,因此不能继承其他类
- 传统的public static final season SPRING = new season(“春天”, “温暖”); 简化成 SPRING(“春天”, “温暖”); 这里必须知道调用的哪个构造器
- 枚举类构造方法只能加private或不加,有多少个实例,就会调用多少次构造方法
- 如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
- 多个枚举对象,使用,间隔,最后用;结尾
- 枚举对象必须放在枚举类的首行
- 枚举类和普通类一样,可以实现接口
枚举常用方法
- values:获取到所有的枚举对象,即数组
注解(Annotation)
- 注解也被称为元数据,用于修饰解释 包、类、方法、属性、构造器、局部变量等信息
- 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入代码的补充信息
- 在JavaEE中占据重要角色
- 三个基本的Annotation:
- @Override 限定某个方法,是重写父类方法。该注解只能用于方法
- @Deprecated 用于表示某个程序元素(类,方法等)已过时
- @SuppressWarnings 抑制编译警告
元注解
- JDK的元注解用于修饰其他注解(Annotation)
12_异常[p444-p459]
- Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError[栈溢出]和OOM(out of memory),Error是严重错误,程序会崩溃。
- Exception:其他因编译错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。Exception分为两大类:运行时异常[程序运行时发生的异常]和编译时异常[编程时编译器检查出的异常]
异常体系图
- 运行时异常,编译器检查不出来。java.lang.RuntimeException类及它的子类都是运行时异常,运行时异常可以不作处理。
- 编译时异常,是编译器要求必须处理的异常,否则代码不能通过编译。
异常处理
- 异常的处理机制:try-catch-finally和throws
try-catch-finally
- 基本语法
try {
可能发生异常的代码
}catch(Exception e) {
如果捕获到异常
1.当异常发生时,系统将异常封装成Exception 对象e传递给catch
2.得到异常对象后,程序员自己处理
}finally {//可以没有finally
不管是否有异常发生,始终会执行finally
通常将释放资源的代码,放在finally
}
- 注意事项
- 如果异常发生了,则try中异常发生语句后面的代码不会执行,直接进入catch
- 如果异常没有发生,则顺序执行try里的代码,不会进入catch
- 如果不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),使用finally
- 可以有多个catch语句,捕获不同的异常,要求父类异常在后,子类异常在前,如果发生异常,只会执行一个catch
- 可以进行try-finally配合使用,相当于没有捕获异常,因此程序执行完finally块会直接退出
throws
- 基本语法
修饰符 返回类型 方法名() throws 异常 { }
- 对于运行时异常,如果没有显示处理,默认throws
- 如果一个方法可能生成某种异常,但是并不确定如何处理这种异常,显示地声明抛出异常,表明该方法将不对这些异常进行处理,由该方法的调用者负责处理。
- 在方法的声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是异常的父类
- 子类重写父类方法时,对于抛出异常的规定:子类重写的方法,所抛出的异常类型必须是父类抛出的异常或父类抛出异常的子类异常
自定义异常
public class CustomException {
public static void main(String[] args) {
int a = 101;
if (a <= 0 && a >=100) {
throw new MyException("a需要在0-100");
}
System.out.println("a在指定范围");
}
}
//一般自定义异常继承RuntimeException,可以使用默认处理机制
class MyException extends RuntimeException{
//通过构造器,设置提示信息
public MyException(String message) {
super(message);
}
}