java基础
java基础语法
注释、标识符、关键字
注释
- 注释不会被执行,写给写代码的人看的
- 可以帮助我们理解
- 单行注释 //
- 多行注释/* */
- 文档注释 /** */
- 写代码要注释规范
标识符
- 所有的标识符都应该以字母(AZ或者az),美元符($)、下划线(_)开始
- 首字符之后可以是字母、美元符、下划线或数字的任意字符组合
- 不能使用关键字作为变量名火方法名
- 标识符是大小写敏感的
- 合法标识符:age、$salary, _value,
- 非法标识符举例:123abc, -salary
- 可以使用中文命名,但是不建议,也不建议用拼音
运算符
实例
- b = a++,执行这行代码之后,先把a=3赋值给了b,所以b输出3,但是a的值变成了4
- c=++a,执行这行代码之前,a先自增变成5,然后再把值赋给c,所以a、c都输出为5
逻辑运算
- 逻辑与运算:两个变量都为真,结果才为真
- 逻辑或运算:两个变量有一个为真,则结果才为真
- 逻辑非运算:如果是真,则变为假,如果是假,则变为真
位运算
-
向左移向右移移动的是数字在二进制中的位置
-
左移一位就相当于乘以2
-
右移一位就相当于除以2
-
32<<1=32*2=64
-
32<<2=32*2^2=128
-
32>>1=32/2=16
连接符
package niu;
public class Demo1 {
public static void main(String[] args) {
int a = 10;
int b = 20;
//字符串连接符 + ,String
System.out.println(""+a+b);//1020,+将两个字符串拼接起来
System.out.println(a+b+"");//30,先进行运算
}
}
三元运算符
package jjj;
//三元运算符
public class Demo2 {
public static void main(String[] args) {
//x ? y : z
//如果x==true,则结果为y,否则结果为z
int score = 20;
String type = score <60 ? "不及格":"及格";//掌握
System.out.println(type);
}
}
用户交互Scanner
scanner对象
-
我们可以通过Scanner类来获取用户的输入
-
基本语法:
-
Scanner scanner= new Scanner(System.in);
-
-
通过Scanner类的next()与nextLine()方法获取输入的字符串,在读取前我们一般需要使用hastNext()与hasNextLine判断是否还有输入的数据
next():
- 一定要读取到有效字符后才可以结束输入
- 对输入有效字符之前遇到的空白,next()方法会自动将其去掉
- 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符
- next()不能得到带有空格的字符串
package scanner;
import java.util.Scanner;
public class Demo01 {
public static void main(String[] args) {
//创建一个扫描器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);
System.out.println("使用next方式接收:");
//判断用户有没有输入字符串
if (scanner.hasNext()) {
//使用next方式接收
String str = scanner.next();
System.out.println("输入内容为:"+str);
}
scanner.close(); //凡是属于IO流的类如果不关闭会一直占用资源,要养成好习惯用完就关掉
}
}
nextLine():
- 以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符
- 可以获得空白
package scanner;
import java.util.Scanner;
public class Demo03 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数据:");
String string = scanner.nextLine();
System.out.println("输入的内容为:"+string);
scanner.close();
}
}
- Next()的其他类型的示例
package scanner;
import java.util.Scanner;
public class Demo05 {
public static void main(String[] args) {
//我们可以输入多个数字,并求其总和和平均数,每输入一个数字用回车确认,通过输入非数字来结束输入并输出执行结果
Scanner scanner = new Scanner(System.in);
//和
double sum = 0;
//计算输入了多少个数字
int m = 0;
//通过循环判断是否还有输入,并在里面对每一次进行求和和统计
while (scanner.hasNextDouble()) {
double x = scanner.nextDouble();
m = m + 1;
sum = sum + x;
System.out.println("你输入了第"+m+"个数据,然后当前结果sum="+sum);
}
System.out.println(m +"个数的和为" + sum);
System.out.println(m+ "个数的平均值是" + (sum/m));
scanner.close();
}
}
Dos命令
打开CMD的方式
- 直接在桌面点开开始+系统+命令提示符
- Win键+R 输入cmd打开控制台
- 在任意文件夹下,按住shift键+鼠标右键点击,在此处打开命令窗口
- 资源管理器的地址栏前面加上cmd路径
管理员方式运行:选择以管理员方式运行
常用的Dos命令
1、#盘符切换 盘+:(英文冒号)
2、#查看当前目录下的所有文件 dir
3、#切换目录 cd cd /d 路径
4、#cd ..返回上一级
5、#清理屏幕 cls
6、#退出终端 exit
7、#查看电脑的ip ipconfig
8、#打开应用
calc
mspaint
notepad
9、#打开网站 ping 命令
10、#文件操作
md 目录名 创建目录
rd 目录名
cd> 文件名 创建文件
del 文件名 删除文件
包机制、JavaDoc *
包机制
-
java提供的包机制,用于更好的组织语言,区别类名的命名空间
-
包的本质实际上就是创建不同的文件夹/目录来保存类文件
-
控制访问范围
-
命名规则:只能包含数字,字母,下划线,小圆点,的那不能用数字开头,不能是关键字或保留字
一般是小写字母+小圆点:com.公司名.项目名.业务模块名
-
包语言的语法格式为
package pkg1[. pkg2[. pkg3…]];
-
一般利用公司域名倒置作为包名: com.woaixuexi.www
-
为了能够使用某一个包的成员,我们需要在Java程序中明确导入该包。使用”import“语句可完成此功能
import pkg1[. pkg2…].(name|*);
-
包名要规范
-
推荐看阿里巴巴开发手册
常用的包:
-
java.lang.* lang包是基础包,默认引入,不需要再引入
java.util.* util包,系统提供的工具包,工具类,使用Scanner
java.net.* 网络包,网络开发
Java.awt.* 是做Java的界面开发,GUI
JavaDoc
- 用来生成自己的API 文档的
- 参数信息
- @author 作者名
- @version 版本号
- @since 知名许哟啊最早使用的角度看版本
- @param 参数名
- @return 返回值情况
- @throws 异常抛出情况
jdk7新特性:数字之间可以用下划线分割,但不影响输出,方便辨认
eclipse :复制当前行到上、下一行Ctrl+alt+上下箭头
idea中时ctrl+d复制到下一行
数据类型*
强类型语言
要求变量的使用要严格符合规矩,所有的变量都必须先定义后才能使用
优点
安全性高
缺点
速度慢
弱类型语言
分类
- 浮点数具有有限,离散,舍入误差,接近但不等于等特点
- 最好完全避免使用浮点数进行比较
- 银行业务表示使用BigDecimal
字符拓展
- 所有的字符本质就是数字
- 二进制 0b开头
- 八进制0
- 十六进制0x
转义字符
类型转换
- java是强类型语言,所以要进行一些运算的时候,需要用到类型转换
- 类型转换从低到高:byte、char、short–>int–>long–>float–>double(小数的优先级高于整数)
- 低优先级转换到高优先级自动转换,自动类型转换,目标类型大于源类型,如 double 类型长度为 8 字节, int 类型为 4 字节,因此 double 类型的变量里直接可以存放 int 类型的数据,但反过来就不可以了
- 在运算中,不同类型的转化为同一类型,然后进行转换
- 不能对布尔值进行转换
- 不能把对象类型转换为不相干的类型
- 在把高容量转换到低容量时,强制转换
- 转换的时候可能存在内存溢出,或者精度问题
强制类型转换
- 格式:(类型)变量名 高–>低
自动类型转换
- 低–>高
变量、常量*
- Java是强类型语言,每个变量都必须要声明其类型
- Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域
注意事项
- 每个变量都有类型,类型可以是基本类型,也可以是引用类型
- 变量名必须是合法的标识符
- 变量声明是一条完整的语句,因此每一个声明都必须以分号结束
变量作用域
类变量
- 也叫静态变量,静态属性,是该类所有对象共享的变量,任何一个该类的对象去访问他时,取到的值都是相同的值,同样任何一个该类对象去修改他是,修改的也是同一个变量
实例变量
- 从属于变量,如果不自行初始化,这个类型的默认值0
- 布尔值:默认值是false
- 除了基本类型,其余的默认值都是null
全局变量
- 也就是属性,作用域为整个类体
- 可以不赋值,直接使用,有默认值
局部变量
-
必须声明和初始化值
-
除了属性之外的其他变量,作用域为定义他的代码块中
-
必须赋值后使用,没有默认值
作用域的注意事项和细节
- 属性和局部变量可以重名,访问时遵循就近原则。
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
- 属性生命周期较长,伴随着对象的创建二创建,伴随的对象的死亡而死亡。局部变量,生命周期短,伴随着他的代码块的创建而执行,伴随着代码块的结束而死亡,即再一次调用过程中。
- 作用域不同
- 全局变量:可以被本类使用,也可以被其他类使用,通过对象调用
- 局部变量:只能在本类中对应的方法中使用
- 修饰符不同
- 全局变量/属性可以加修饰符
- 局部变量不可以加修饰符
变量命名规范
- 所有比那辆,方法,类名:见名知意
- 类成员变量:首字母小写和驼峰原则:monthSalary
- 局部变量:首字母小写和驼峰原则
- 常量大写字母和下划线:MAX_VALUE
- 类名:首字母大学和驼峰原则
- 方法名:首字母小写和驼峰原则:runRun()
常量
- 初始化后不能在改变值不会变动的值
- 一种特殊的变量,它的值贝设定后,在程序运行过程中不允许被改变
- 常量名一般使用大写字符
- 修饰符不存在先后顺序
- final是常量的关键字
控制结构
顺序结构
- java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行
- 最简单的算法结构
- 语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构
分支
选择结构*
if单选择结构
- 语法:
if(布尔表达式){
}
if双选择结构
- 有两个选择,如果跟选择一相同,则布尔表达式的值是true,执行花括号中的语句;如果跟第一个选择不一样,则布尔表达式的值为false,执行else中的语句
if(布尔表达式){
//如果布尔表达式中的值为true,执行代码
}else{
//如果布尔表达式的结果为flase,执行代码
}
- 代码示范
-
package struct; import java.util.Scanner; public class Demo002 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("请输入成绩:"); int score = scanner.nextInt(); if (score>=60) { System.out.println("成绩合格!"); }else { System.out.println("成绩不合格!"); } scanner.close(); } }
if多选择结构
-
在else上添加新的if
if(布尔表达式1){ //如果布尔表达式1中的值为true,执行代码 }else if(布尔表达式2){ //如果布尔表达式2的结果为true,执行代码 }elseif(布尔表达式3){ //如果布尔表达式中3的值为true,执行代码 }else{ //如果以上布尔表达式的结果都为flase,执行代码 }
- 只要其中一个else if的语句检测为true,其他的else if 以及else语句都将跳过执行
嵌套的if结构
- 使用嵌套的if…else语句是合法的,也就是说你可以在另一个if或者else if语句中使用if或者else if 语句。你可以向if语句一样嵌套else if……else
switch多选择结构
-
switch case 语句判断一个变量与一个系列中某个值是否相等,每个值称为一个分支
-
switch语句中的变量类型可以是:
-
byte、short、int或者char
-
从java SE 7 开始
-
switch支持字符串String类型了
-
同时case标签必须为字符串常量或字面量
-
表达式数据类型,应和case后面的常量类型一直,或者时可以自动转换成可以相互比较的类型
-
case子句中的值必须时常量,而不能是变量
-
switch(表达式)中的返回值必须是(byte ,short,int, char,enum(枚举),String)
-
default子句是可选的,当没有匹配的case是,执行default
-
break语句是用来在执行一个case分支后是程序跳出switch语句块;如果没有写break,程序会执行到switch结尾
-
如果没有default子句,有没有匹配任何常量,则没有输出
-
package struct;
public class SwitchDemo01 {
public static void main(String[] args) {
//case穿透
char grade = 'C';
switch (grade) {
case 'A':
System.out.println("优秀");
break;//可选,每个case后必须加上break来结束循环
case 'B':
System.out.println("良好");
break;
case 'C':
System.out.println("及格");
break;
case 'D':
System.out.println("再接再厉");
break;
case 'E':
System.out.println("挂科");
break;
default:
System.out.println("未知等级");
}
}
}
循环
循环结构*
- Java5中引入了一种主要用于数组的增强型的for循环
while循环
-
while是最基本的循环,结构为
-
while(布尔表达式){ //循环内容 }
-
-
只要布尔表达式为true,循环就会一直执行下去
-
while循环时先判断在执行语句
-
我们大多数情况下是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环
-
少数情况需要循环一直执行,比如服务器的请求响应监听等
-
循环条件一直为true就会造成无限循环【死循环】,我们正常的业务变成中尽量避免死循环,会影响程序性能或者在成程序卡死崩溃
package struct;
public class WhileDemo04 {
public static void main(String[] args) {
int i = 0;
while(i<100) {
i++;
System.out.println(i);
}
}
}
do…while循环
- 对于while语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次
- do…while…循环和while循环相似,不同的是,do……while循环会至少执行一次
do{
//代码语句
}while(布尔表达式)
- while先判断后执行,do……while是先执行后判断!
- do……while总是保持循环体会被至少执行一次
package struct;
public class DoWhileDemo02 {
public static void main(String[] args) {
int a = 0;
while (a<0) {
System.out.println(a);//无输出结果
a++;
}
System.out.println("===============");
do {
System.out.println(a);//输出0
a++;
} while (a<0);
}
}
for循环**
-
for循环语句是支持迭代的一种通用结构,是最有效,最灵活的循环结构
-
循环条件是返回一个布尔值的表达式
-
for(;循环判断条件;)中的初始化值和变量迭代可以写到其他地方,但是两边的分号不能省略。
-
循环初始值可以有多条初始化语句,但要求类型一样,并且中间用逗号隔开,需要你换变量迭代也可以有多条变量迭代语句,中间用逗号隔开
-
for循环执行的次数是在执行前就确定的。格式:
-
for(初始化;布尔表达式;更新){ //代码语句 }
-
-
import java.util.Scanner; //空心菱形 public class Demo4 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); for (int i = 1; i <=a ; i++) {//定义行数 for (int j = a; j >= i ; j--) {//定义列数 System.out.print(" "); } for (int j = 1; j <=2*i-1 ; j++) { if (j == 1||j==2*i-1) {//判断条件,只在第一个和最后一个位置上输出字符 System.out.print("*"); }else { System.out.print(" "); } } System.out.println(); } for (int i = a-1; i >0 ; i--) {//再次循环,进行下边倒三角的实现 for (int j = a; j >= i ; j--) { System.out.print(" "); } for (int j = 1; j <=2*i-1 ; j++) { if (j == 1||j==2*i-1) { System.out.print("*"); }else { System.out.print(" "); } } System.out.println(); } }
增强for循环
-
Java5引入了一种主要用于数组或集合的增强型for循环
-
格式:
for(声明语句: 表达式){ //代码句子 }
-
声明语句:生命新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等
-
表达式:表达式是要访问的数组名,或者时返回值为数组的方法
- 练习
package struct;
public class ForDemo01 {
public static void main(String[] args) {
//while循环和for循环输出1-100
int a = 1;//初始化条件
while(a<=100) {//条件判断
System.out.println(a);//循环体
a+=1;//迭代
}
System.out.println("while循环结束!");
//初始化值//条件判断//迭代
for(int i = 1;i<=100;i++) {
System.out.println(i);
}
System.out.println("for循环结束!");
}
}
package struct;
public class ForDemo02 {
public static void main(String[] args) {
//练习:计算0~100之间的奇数和偶数之和
int oddsum = 0;
int evensum = 0;
for(int i = 0;i<=100;i++) {
if (i%2!=0) {//奇数
oddsum+=i;
}else {//偶数
evensum+=i;
}
}
System.out.println("奇数的和:"+oddsum);
System.out.println("偶数的和:"+evensum);
}
}
package struct;
public class ForDemo03 {
public static void main(String[] args){
//练习:用while或者for循环输出1-1000之间能被5整除的数,并且每行输出3个
for(int i=0;i<=1000;i++) {
if (i%5 == 0) {
System.out.print(i+"\t");
}
if (i%(5*3)==0) {
System.out.println();
}
}
//println 输出完会换行
//print 输出完不会换行
}
}
package struct;
public class ForDemo04 {
public static void main(String[] args) {
//for循环输出99乘法表
for(int i=1;i<10;i++){
for(int j = 1;j<=i;j++) {//j<=i避免重复的问题
System.out.print(j+"*"+i+"="+j*i+"\t");
}
System.out.println();
}
}
}
break
- break在任何循环语句的主体部分,均可用break控制循环的流程
- break用于强行推出循环,不执行该循环中剩余的语句。(break语句也可在switch中使用)
- break语句出现在多层嵌套的语句块中时,可以通过标签知名要终止的时那一层语句块
continue
- continue语句用在循环语句体中,用于终止本次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定
打个比方:break就是离职,continue就是请假
goto关键字
数组
数组概述
- 相同类型数据的有序集合
- 数组描述的是相同类型的若干数据,按照一定的先后顺序排列组合而成
- 每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问他们
数组声明创建
- 首先想要使用数组,必须先声明数组,数组的声明格式:
datatype[] arrayRefVar;
int[] nunmbers;
datatype arrayRefVar[];
- java语言可以使用new操作符来创建数组
int[] num = new int[10];
- 数组的元素是通过索引访问的,数组索引从0开始
- 获取数组长度: 数组名.length
值传递和引用传递的区别
public static void main(String[] args) {
//基本数据类型赋值,赋值方式为值拷贝
//n2的变化,不会影响到n1的值
int n1 = 10;
int n2 = n1;
n2 = 20;
System.out.println("n1 = "+n1);//10
System.out.println("n2 = "+n2);//20
//数组在默认情况下是引用传递,赋的值是地址,赋值方式为引用赋值
//是一个地址,arr2变化会影响到arr1
int[] arr1 = {1,2,3};
int[] arr2 = arr1;//把arr1赋给arr2
arr2[0] = 2;
System.out.println("arr1的元素:");
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
}
/*
值传递和引用传递的区别
首先jvm内存分为栈,堆,方法区
值传递的例子 int n1 = 10;
int n2 = n1;
值传递就是在栈中给两个变量赋值,在栈中分别定义出两个变量,并赋给值,知识单纯的把值赋给了变量,当一个变量变化,另一个变量的值不会发生变化
引用传递例子: int[] arr1 = {1,2,3};
int[] arr2 = arr1;//把arr1赋给arr2
引用传递则是先在栈中定义出两个变量,并在堆中开辟一个地址空间,专门来存放数据,这里的arr1和arr2就相当于这个空间的两个门。
在这个地址空间中的数据会发生变化,对arr1和arr2都有影响,
数组在默认情况下是引用传递,输出的值也是地址中存放的数据,arr1和arr2的数据是共享的
*/
初始化
静态初始化
- 在创建数组的时候就对数组进行初始化赋值,初始化之后数组个数和元素不能改变
//静态初始化:创建 +赋值
int[] a = {1,2,3,4,5,6,7,8,9};
System.out.println(a[1]);
动态初始化
- 声明数组并创建完空间后,里面全是默认值,需要进行赋值
//动态初始化:包含默认初始化
int[] b = new int[10];
b[0] = 10;//进行初始化赋值
b[1] = 1;
System.out.println(b[0]);//10,输出第一个数
System.out.println(b[1]);//1
System.out.println(b[2]);//0
System.out.println(b[3]);//0
-
默认值:int 0,short 0, byte 0, long 0, float 0.0, double 0.0, char \u0000, boolean false,
String null;
数组边界
- 下标的合法区间:[ 0,length-1],如果越界就会报错;
public static void main(String[] args){
int[] a= new int[2];
System.out.println(a[2]);
}
- ArrayIndexOutOfBoundsException: 数组下标越界异常!
- 小结:
- 数组是相同的数据类型的有序集合
- 数组也是对象。数组元素相当于对象的成员变量
- 数组长度是确定的,不可变的。如果越界,则报:ArrayIndexOutOfBounds
数组使用*
-
普通for循环
-
For-Each循环
-
数组作方法入参
-
数组作返回值
package array;
public class ArraysDemo02 {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//JDK1.5,没有下标
for (int array : arrays) {
System.out.println(array);
}
}
}
package array;
public class ArraysDemo02 {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//JDK1.5,没有下标
// for (int array : arrays) {
// System.out.println(array);
// }
//printArrays(arrays); //打印数组元素
int[] reverse = reverse(arrays);//反转数组
printArrays(reverse);
}
//反转数组
public static int[] reverse(int[] arrays) {
int[] result = new int[arrays.length];
//反转操作
for (int i = 0,j = result.length-1; i < result.length; i++,j--) {
result[j] = arrays[i];
}
return result;
}
//打印数组元素
public static void printArrays(int[] arrays) {
for (int i = 0; i < arrays.length; i++) {
System.out.print(arrays[i]+" ");
}
}
}
多维数组
- 多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组
- 二维数组
int a[][] = new int[2][5];
- 上面创建的二维数组可以看成一个两行五列的数组
Arrays类*
-
数组的工具类Java.util.Arrays
-
由于数组对象本身并没有什么方法供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作
-
查看JDK帮助文档
-
Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而“不用”使用对象来调用
-
具有以下常用功能:
- 对数组赋值:通过fill方法
- 对数组排序:通过Sort方法,按升序
- 比较数组:通过equals方法比较数组中元素值是否相等
- 查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作
稀疏数组
- 当一个数组中大部分元素为0,或者为统一值的数组时,可以使用稀疏数组来保存该数组
- 稀疏数组的处理方式是
- 记录数组一共有几行几列,有多少不同值
- 吧具有不同值的元素和行列及值记录再一个小规模的数组中,从而缩小程序的规模
- 如下图:左边是原始数组,有比那是稀疏数组
package array;
public class ArrayDemo8 {
public static void main(String[] args) {
//1.创建一个二维数组11*11 0:没有棋子 1:黑棋 2:白棋
int[][] array1 = new int[11][11];
array1[1][2] = 1;
array1[2][3] = 2;
//输出原始的数组
System.out.println("输出原始的数组");
for (int[] ints : array1) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
//转换为稀疏数组保存
//获取有效值的个数
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (array1[i][j] !=0) {
sum++;
}
}
}
System.out.println("有效值的个数是:"+sum);
//2.创建一个稀疏数组
int[][] array2 = new int[sum+1][3];
array2[0][0] = 11;
array2[0][1] = 11;
array2[0][2] = sum;
//便利二维数组,将非零的值,存放再稀疏数组中
int count = 0;
for (int i = 0; i < array1.length; i++) {
for (int j = 0; j < array1[i].length; j++) {
if (array1[i][j] != 0) {
count++;
array2[count][0] = i;
array2[count][1] = j;
array2[count][2] = array1[i][j];
}
}
}
//输出稀疏数组
System.out.println("稀疏数组");
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i][0]+"\t"+array2[i][1]+"\t"+array2[i][2]+"\t");
}
System.out.println("============");
System.out.println("还原");
//1.读取稀疏数组
int[][] array3 = new int[array2[0][0]][array2[0][1]];
//2.给种种的元素还原他的值
for (int i = 1; i < array2.length; i++) {
array3[array2[i][1]][array2[i][1]]=array2[i][2];
}
//3.打印
//输出原始的数组
System.out.println("输出还原的数组");
for (int[] ints : array3) {
for (int anInt : ints) {
System.out.print(anInt+"\t");
}
System.out.println();
}
}
}
Java方法详解
什么是方法
- 例:System.out.println(),意思就是,用System类里面的标准输出对象out中的println方法
- Java方法是语句的集合,他们在一起执行一个功能
- 方法是解决一类问题的步骤的有序组合
- 方法包含于类或对象中
- 方法在程序中被创建,在其他地方被引用
- 设计方法原则:方法的本意是功能块,就是实现某个功能的语言块的集合。一个方法只完成1个功能,这样有利于我们后期的扩展
方法的定义和调用*
方法的定义
- 方法包含一个方法和一个方法体。以下是其所有部分
-
修饰符,可选的,告诉编译器如何调用该方法,定义了该方法的返回类型
-
返回值数据类型:方法可能会返回值returnValueType是方法返回值的数据类型。有些方法执行所需要的操作,但是没有返回值,在这种情况下,returnValueType是关键字void
- 一个方法最多有一个返回值(可以用数组返回多个结果)
- 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为return 值;而且要求返回值类型必须和return的值类型一直或兼容
-
方法名:方法的实际名称方法名和参数共同构成方法签字,驼峰命名法,要做到见名知意
-
参数类型:参数像一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数
- 形式参数:在方法被调用时用于接受外界输入的数据
- 实参:调用方法时实际传给对方的数据
- 形参列表:
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int n1,int n2)
- 参数类型可以为任意类型,包含基本类型和因哟个类型,比如printArr(int[] a)
- 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
- 方法定义是的参数成为形式参数,简称形参
-
方法体:方法体包含具体的语句,定义该方法的功能,方法体的语句可以为输入,输出,变量,运算,分支,循环,方法调用,但里面不能在定义方法!即:方法不能嵌套定义。
-
/*修饰符 返回值类型 方法名(参数类型 参数名){
……
方法体
……
return 返回值;
}*/
方法的调用
- 调用方法:对象名.方法名(实参列表)
- Java支持两种调用方法,根据方法是否返回值来选择
- 当方法返回一个值的时候,方法调用通常被当作一个值。例
int larger = max(30,40);
-
- 如果方法返回值是void,方法调用一定是一条语句
system.out.println("Hello,World!");
- 当程序执行到方法时,就会在jvm内存中开辟一个空间(栈空间)
- 当方法执行完毕或者执行到return语句,就会i将返回值返回给调用语句
- 返回调用方法的地方,同时会释放开辟的独立的空间(方法的栈空间)
- 继续执行下面的语句
- 当main方法(栈),执行完毕后,释放main方法开辟的栈空间,退出程序(main栈在最底层)
方法调用的细节:
- 同一个类中的方法之间调用,直接调用即可
- 跨类的方法A类调用B类方法:需要通过对象名调用
- 跨类的方法调用和防火阀修饰符相关
package method;
public class Demo0102 {
public static void main(String[] args) {
int max = max(10, 10);
System.out.println(max);
}
//比大小
public static int max(int n1,int n2) {
int result = 0;
if (n1 == n2) {
System.out.println("n1 == n2");
return 0;//终止方法
}
if (n1>n2) {
result = n1;
} else {
result = n2;
}
return result;
}
}
方法的传参机制
- 引用数据类型的传参机制:
- 在B类中编写一个方法test100,可以接受一个数组,在方法中修改该数组,看看原来的数组是否变化? 会变化
B类中编写一个方法test200,可以接收一个Person(age)对象,在方法中修改对象属性,看看原来对象的属性是否变化? 会变化
- 引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参
import java.util.Arrays;
public class MethodParameter01 {
public static void main(String[] args) {
int[] a = {1,3,6,8,7};
System.out.print(Arrays.toString(a));
B b = new B();
b.test100(a);
System.out.print(Arrays.toString(a));
Person1 p = new Person1();
p.age = 10;
System.out.println(p.age);
b.test200(p);
System.out.println(p.age);
}
}
class Person1{
int age = 10;
}
class B{
public void test200(Person1 p){
p.age = 100;//修改了类的属性
}
public void test100(int[] a){
for (int i = 0; i < a.length; i++) {
a[i] =i;
}
System.out.print(Arrays.toString(a));
}
}
test100数组的内存分析
test200类的属性的内存分析
方法的重载*
- 重载就是在一个类中,有相同的函数名称,但形参不同的函数
- 方法重载的规则:
- 方法名称必须相同
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同)
- 方法的返回类型可以相同也可以不相同
- 仅仅返回类型不同,不足以成为方法的重载
- 方法名称相同时,编译器会根据调用的方法的参数个数、参数类型等去逐个匹配,已选择对应的方法,如果匹配失败,则编译器报错。
package method;
public class Demo0102 {
public static void main(String[] args) {
int max = max(10,20);//根据
System.out.println(max);
}
//比大小
public static double max(double n1,double n2) {
double result = 0;
if (n1 == n2) {
System.out.println("n1 == n2");
return 0.0;//终止方法
}
if (n1>n2) {
result = n1;
} else {
result = n2;
}
return result;
}
public static int max(int n1,int n2) {
int result = 0;
if (n1 == n2) {
System.out.println("n1 == n2");
return 0;//终止方法
}
if (n1>n2) {
result = n1;
} else {
result = n2;
}
return result;
}
public static int max(int n1,int n2,int n3) {
int result = 0;
if (n1 == n2&&n2 == n3) {
System.out.println("n1 == n2 == n3");
return 0;//终止方法
}
if (n1>n2) { if(n1>n3) {
result = n1;
}else {
result = n3;
}
} else {if(n2>n3) {
result = n2;
}else {
result = n3;
}
}
return result;
}
}
命令行传参
- 有时候你希望运行一个程序的时候再传递给它消息。这要靠传递命令行参数给main()函数实现
可变参数
-
java1.5开始,Java支持传递同类型的可变参数给一个方法
-
Java允许将同一个类中多个同名同功能单参数个数不同的方法,封装成一个方法。
-
//int··· 表示接收的是可变参数,int类型,即可接收多个int,可以当作一个数组
-
在方法声明中,在指定参数类型后加一个省略号(……)
-
一个方法中只能指定一个可变参数,他必须是方法的最后一个参数。任何普通的参数必须在他之前声明
递归**
-
A方法调用A方法,自己调用自己就是递归
-
利用递归可以用简单的程序来解决一些复杂的问题。他通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。递归的能力在于有限的语句来定义对象的无限集合
-
重要规则:
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量时独立的,不会相互影响,比如n变量
- 如果方法中使用的时引用类型变量(比如数组),就会共享该引用类型的数据
- 递归必须想退出递归的条件逼近,斗则就是无线递归,出现StackOverflowError
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,通过是当方法执行完毕或者返回时,该方法也就执行完毕
-
递归结构包括两个部分
- 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环
- 递归体:什么时候需要调用自身方法
Java api
oop
初识面向对象
思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要那些分类,然后对这些分类进行单独思考,最后,才对某个分类下的细节进行面向过程的思考
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
- 对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
- 面向对象的本质就是:以类的方式组织代码,以对象的形式组织(封装)数据*
- 抽象
- 特性:封装、继承、多态
- 从认识的角度思考是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象
- 从代码运行角度考虑是先有类后有对象,类是对象的模板。
public class OopDemo1 {
public static void main(String[] args) {
//创建Cat对象
//cat1是对象名(对象引用)
//new Cat() 创建的对象空间(数据),才是真正的对象
Cat cat1 = new Cat();
cat1.age = 5;
System.out.println(cat1.age);
cat1.say();
}
//对象的属性默认值,遵守数组规则
//默认值:int 0,short 0, byte 0, long 0, float 0.0, double 0.0, char \u0000, boolean false,String null;
//当不进行初始化赋值时,都是默认初始化值
}
class Cat{
String name;//属性
int age;//属性
String color;//属性
public void say(){//行为
System.out.println("叫了一声");
}
}
对象的创建
- 类是一种抽像的数据类型,他是对某一类事物整体描述/定义,但是并不能代表某一个事物的具体的事物
- 成员变量 = 属性 = field(字段)
- 属性是类的一个组成部分,一般是基本数据类型,也是引用类型(对象,数组)。比如定义一个猫类的int age 就是属性
- 先声明在创建
Cat cat;//声明对象cat
cat = new Cat();//创建
- 直接创建
Cat cat = new Cat();
- 对象是抽象概念的具体的实例
- 使用new关键字创建对象
- 使用new关键字创建对象的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构建器的调用
- 类中的构造器也成为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
- 构造器必须要掌握
构造方法/构造器
-
基本语法: [修饰符] 方法名(形参列表){方法体;}
-
构造器是类的一种特殊的方法,他的作用是完成堆新对象的初始化
-
构造器的修饰符可以是默认,也可以是public,protected,private
-
构造器没有返回值
-
方法名和类名字必须一样
-
参数列表和成员方法一样的规则
-
构造器的调用由系统完成
public class VarScopeDetail { public static void main(String[] args) { I i = new I("sheng",22); System.out.println("年龄:"+i.age); System.out.println("姓名:"+i.name); } } class I{ int age; String name; public I(String names,int ages){ System.out.println("构造器被调用!"); name = names; age = ages; } }
注意事项和使用细节
- 一个类可以定义多个不同的构造器,即构造器重载
- 构造器名和类名相同,且没有返回值
- 构造器是完成对对象的初始化,并不是创建对象
- 在创建对象是,系统自动的调用该类的构造方法
- 如果程序员没有定义构造方法,系统会自动给雷生成一个无参构造方法(默认构造方法)
- 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器除非显示的定义一下
- 将构造器和setxx结合在一起,确保数据的安全性和隐私性
class Person{
int age = 60;
String name;
public Person(int ages,String names){
age = ages;
name = names;
}
}
Person p = new Person(20,"小明");
/*
流程分析:
1.执行Person11 p1 = new Person11();加载Person类信息(属性,方法),只加载一次
2.在堆中分配空间(地址)
3.完成对象的初始化,(1.默认初始化age = 0;name = null;2.显式初始化age = 60,name = null;3.构造器初始化age = 20,name = 小明)
4.把对象在堆中的地址返回给p
*/
封装
-
把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作
-
封装的好处和理解
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
-
该漏的漏,该藏的藏
- 程序设计要追求”高内聚,低耦合“。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合:仅暴露少量的方法给外部使用
-
封装(数据的隐藏)
- 通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口有来访问,这称为信息隐藏
-
属性私有,get(获取属性的值)/set(判断属性并赋值)
-
/*1.提高程序的安全性,保护数据 2.隐藏代码的实现细节 3.统一接口 4.增加了系统的可维护性 */
继承
- 继承的本质是对某一批类的抽象,从而事项对现实世界更好的建模,当子类创建好后,建立查找的关系
- 继承可以解决代码复用,让我们的编程更加靠近人类思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可
- extends的意思是“扩展”。子类是父类的拓展,继承后子类自动拥有父类定义的属性
- 继承所带来的便利:
- 代码的复用性提高了
- 代码的拓展性和维护性提高了
细节
- 子类继承了所有的属性和方法,但是私有属性不能再子类直接访问,要通过公共的方法去访问
- 子类必须调用父类的构造器,完成父类的初始化
- 当创建对象时,不管使用子类的那个构造器,则必须再子类的构造器中用super去指定使用父类的那个构造器完成对付类的初始化工作,否则,编译不会通过,如果父类中没有写构造器,则默认调用无参构造器,如果父类中写了有参构造器,则必须在子类的构造器中写明调用的是父类的那个构造器
- 如果希望指定去调用父类的某个构造器,则显示的调用一下:super(参数列表)
- super在使用时,需要放在构造器第一行
- super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
- java所有类都是Object类的子类
- 父类构造器的调用不限于直接父类!将一直网上追溯知道Object类(顶级父类)
- 子类最多只能继承一个父类(直接继承),即Java中式但继承机制
- 不能滥用继承,子类和父类之间必须满足is-a的逻辑关系
- Java中只有单继承没有多继承
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖,组合,聚合等
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示
public class Test {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//这时应该注意,要按照查找关系来返回信息
//1.首先看子类是否有该属性
//2.如果子类有这个属性,并且可以访问,则返回信息
//3.如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息)
//4.如果父类没有按照3.的规则,继续招商及父类,知道Object
System.out.println(son.name);
System.out.println(son.age);
System.out.println(son.hobby);
}
}
class Son extends Father{
String name = "大头儿子";
}
class Father extends GrandePa{
String name = "大头爸爸";
int age = 53;
}
class GrandePa {
String name = "大头爷爷";
String hobby = "旅游";
}
- object类
public class Person {
public Person() {
System.out.println("Person无参执行了");
}
protected String name = "shenghao";
//public 公共的 优先级最高
//protected 受保护的
//default 默认的
//private 私有的 优先级最低
private int money = 1000;
public void say() {
System.out.println("shuojuhua");
}
public void print(){
System.out.println("Person");
}
//私有的方法无法被继承
public int getMoney() {
return this.money;
}
public void setMoney(int money) {
this.money = money;
}
}
//学生 : 派生类 子类
//子类继承了父类,就会拥有父类的全部方法
public class Student extends Person{
public Student() {
//隐藏代码,调用了父类的无参构造
super();//调用父类的构造器,必须压迫在子类构造器的第一行
System.out.println("Student无参执行了");
}
private String name = "sheng";
public void print(){
System.out.println("Student");
}
public void test1(){
print();
this.print();
super.print();
}
public void test(String name){
System.out.println(name);//盛浩
System.out.println(this.name);//sheng
System.out.println(super.name);//shenghao
}
public class Application {
public static void main(String[] args) {
Student student = new Student();
Person p1 = new Person();
student.say();
student.setMoney(100);
System.out.println(student.getMoney());
student.test("盛浩");
student.test1();
}
package Test2;
/*
编写Computer类,包含CPU,内存,硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性[品牌brand]
编写NotePad子类,继承Computer类,添加特有属性[演示color]
编写Test类,在main方法中创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法打印输出信息
*/
public class Computer {
public String cpu;//芯片
public int memory;//内存
public String hard;
public Computer(String cpu,int memory,String hard){
this.hard = hard;
this.memory = memory;
this.cpu = cpu;
}
public String getDetails(){
return "cpu:"+cpu+" memory:"+memory+" hard:"+hard;
}
}
class PC extends Computer{
//编写PC子类,继承Computer类,添加特有属性[品牌brand]
public String brand;//品牌
public PC(String brand,String cpu,int memory,String hard){
super(cpu,memory,hard);
}
public void showInfo(){
System.out.println("品牌"+brand+getDetails());
}
}
class NotePad extends Computer{
//编写NotePad子类,继承Computer类,添加特有属性[演示color]
public String color;
public NotePad(String color,String cpu,int memory,String hard){
super(cpu,memory,hard);
}
public void showInfo(){
/*
找getDetails的方法时(getDetails()和this.getDetails())的顺序是:
首先找本类,如果有,则调用
再找父类,如果有,则调用
父类找不到,继续往上找,一直到Object类
提示:在查找方法的过程中,如果找不到,则提示方法不存在
在查找方法的过程中,如果找到了,但是不能访问,则报错
this.方法名:从本类开始找
super.方法名:从父类开始找
*/
System.out.println(getDetails()+"颜色:"+color);
}
}
//编写Test类,在main方法中创建PC和NotePad对象,分别给对象中特有的属性赋值,
// 以及从Computer类继承的属性赋值,并使用方法打印输出信息
public class ComTest {
public static void main(String[] args) {
PC pc = new PC("华为","rtx4090",1024,"C盘");
NotePad notePad = new NotePad("white","rtx4090t",1024,"d盘");
pc.showInfo();
notePad.showInfo();
}
}
this
- Java虚拟机会给每个对象分配this,代表当前对象
- 那个对象被调用,this就指向那个方法,this就好像在说我是。
注意事项和使用细节
- this关键字可以用来访问本类的属性,方法,构造器
- this用于区分当前类的属性和局部变量
- 访问成员方法的语法:this.方法名(参数列表)
- 访问构造器语法:this(参数列表);注意只能在构造器中使用,并且只能是第一句
- this不能再类定义的外部使用,只能再类的方法中使用
-
super
-
调用父类的构造器,必须压迫在子类构造器的第一行
-
访问父类的属性,但不能访问父类的private属性 super.属性名
-
访问父类的方法,不能访问父类的private方法 super.方法名(参数列表)
-
调用父类的构造器的好处,(分工明确,父类属性由父类初始化,子类属性有子类初始化)
-
当子类中有父类的成员(属性和变量)崇明是,为了访问父类的成员,必须通过super。如果没有重名,使用super,this,直接访问是一样的效果
-
super的访问不限于直接父类,如果父类的父类和本类也有同名的成员,也可以使用
-
super去访问爷爷类的成员,如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则
-
访问父类的构造器
- super(参数列表);只能放在构造器的第一句!
-
super注意点: 1.super调用父类的构造方法,必须在构造方法的第一行 2.super必须只能出现在子类的方法或者构造方法中 3.super和this不能同时调用构造方法 vs this: 代表的对象不同: this:本身调用者这个对象 super:代表父类对象的应用 前提 this:没有继承也可以使用 super :只能在继承条件下才可以使用 构造方法: this();本类的构造 super();父类的构造
-
访问修饰符
使用的注意事项
-
修饰符可以用来修饰类中的属性,成员方法及类
-
只有默认的和public才能修饰类,并且遵循上述访问权限的特点
-
成员方法的访问规则和属性完全一样
-
方法重写
- 基本介绍:
- 简单的说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称,返回类型,参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法
- 方法重写也叫方法覆盖,需要满足下面的条件
- 子类的方法的形参列表,方法名称要和父类方法的参数,方法名称完全一样。
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
- 子类方法不能缩小父类方法的访问权限 public >protected>默认>private,父类如果是public,子类就不能是peotected
- 基本介绍:
public class A extends B{
//Override重写
@Override//注解:有功能的注释!
public void test() {
System.out.println("A=>test()");
}
}
//重写都是方法的重写,和属性无关
public class B {
public void test() {
System.out.println("B=>test()");
}
}
public class Application1 {
public static void main(String[] args) {
//静态方法和非静态方法区别很大
//静态方法: 方法的调用之和左边,定义的数据类型有关
//非静态:重写
A a = new A();
a.test();//A
//父类的引用指向了子类
B b = new A();//子类重写了父类的方法
b.test();//B
}
}
/*
重写:需要有继承关系,子类重写父类的方法
1.方法名必须相同
2.参数列表必须相同
3.修饰符:范围可以扩大:public > protected > default > private
4.抛出的异常:范围,可以被缩小,但不能扩大:
重写,子类的方法和父类必须要一直,方法体不同
为什么要重写:
1.弗雷德功能,子类不一定需要,或者不一定满足
*/
多态,上下转型
- 同一方法可以根据发送对象的不同而采取多种不同的行为方式
- 方法或对象具有多种形态,是面向对象的第三大特征,多条是建立在封装和继承基础之上的
- 一个对象的实际类型是确定的,单可以指向对昂的引用类型有很多(父类,有关系的类)
- 多态的具体体现:
- 方法的多态 重新二和重载就体现多态
- 对象的多态:
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义是 等号的左边,运行类型看等号的右边
- 注意:多态是方法的多态,属性没有多态性
- 前提:两个对象(类)存在继承关系
- 多态的向上转型
- 本质:父类的引用指向了子类的对象
- 语法:父类类型 引用名 = new子类类型();
- 特点:编译类型看左边,运行类型看右边
- 可以调用父类中的所有成员,需要遵循访问权限,不能调用子类中的特有成员,最终运行效果看子类的具体实现
- 多态向上转型调用方法的规则如下:
- 可以调用父类中的所有成员(须遵循访问权限)
- 但是不能掉哟个子类的特有的成员
- 因为在编译阶段,能调用那些成员,是由编译类型来决定的
- 最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法
- 然后调用,跟方法调用的规则一致
- 多态向下转型:
- 语法:子类类型 引用名 = (子类类型)父类引用;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的时当前忙于表类型的对象
- 向下转型后可以调用子类类型中所有的成员
public class Person {
public void run(){
System.out.println("run");
}
}
/*
多态注意事项:
1.多态是方法的多态,属性没有多态
2.子类和父类,有联系 类型转换异常 ClassCastException!
3.存在条件: 继承关系,方法要重写,父类引用指向子类对象!Father f1 = new Son();
无法被重写的方法:
1.static 方法,属于类,它不属于实例
2.final 常量
3.private方法
*/
public class Student extends Person{
public void run(){
System.out.println("son");
}
public void eat(){
System.out.println("eee");
}
public void go(){
System.out.println("go");
}
}
public class Application {
public static void main(String[] args) {
//一个对象的实际类型是确定的
//new Student();
//new Person();
//可以指向的引用类型就不确定了:父类的引用指向子类
// Student 能调用的方法都是自己的或者继承父类的
Student s1 = new Student();
//Person 父类型,可以指向子类,但是不能调用子类独有的方法
Person s2 = new Student();
Object s3 = new Student();
//对象能执行那些方法,主要看对象左边的类型,和右边那关系不大!
//如果这个方法只有父类有,那么父类和子类都可以调用
//如果这个方法只有子类有,则父类需要强制类型转换后才可以调用
//如果这个方法,父类和子类都有的情况下,但是输出的结果不同,优先调用子类的
s2.run();//子类重写了父类的方法,执行了子类的方法
s1.run();
((Student)s2).eat();//强制类型转换
}
}
-
属性没有重写之说,属性的值看编译类型
-
public class A {//父类 public int count = 10;//属性 } class B extends A{//子类 public int count = 20;//属性 }
-
public class Test1 { public static void main(String[] args) { A a = new B();//向上转型 System.out.println(a.count);//10,跟编译类型有关,和运行类型无关 B b = new B(); System.out.println(b.count);//20,编译类型时B, } }
-
instanceof(类型转换)引用类型:用于判断对象的运行类型是否位xx类型或xx类型的子类型
public class Application1 {
public static void main(String[] args) {
//Object > Person > Student
//Object > Person > Teacher
//Object > String
Object object = new Student();
//System.out.println(X instanceof Y); //能不能编译通过,主要看X跟Y有没有父子关系
//创建对象是以子类
System.out.println(object instanceof Student);//true
System.out.println(object instanceof Person);//true
System.out.println(object instanceof Object);//true
//Teacher是Person的另一个子类,跟Student不存在继承关系,所以输出的结果是false
System.out.println(object instanceof Teacher);//false
//Sting类是Object的一个子类,与Student不存在继承关系
System.out.println(object instanceof String);//false
System.out.println("=======================");
Person person = new Student();
System.out.println(person instanceof Student);//true
System.out.println(person instanceof Person);//true
System.out.println(person instanceof Object);//true
//Teacher是Person的另一个子类,跟Student不存在继承关系,所以输出的结果是false
System.out.println(person instanceof Teacher);//false
//编译报错,Person和String同为Object的子类,同级之间不能转换
//System.out.println(person instanceof String);
System.out.println("================");
Student student = new Student();
System.out.println(student instanceof Student);//true
System.out.println(student instanceof Person);//true
System.out.println(student instanceof Object);//true
//编译错误,Student和Teacher同为Person的子类,同级,同级之间不能相互转换
//System.out.println(student instanceof Teacher);
//String是Object的子类,与Student没有继承关系
//.out.println(student instanceof String);
}
}
public class Application2 {
public static void main(String[] args) {
//类型之间的转换: 父类 子类
//高 低
Person person = new Student();
//需要将person这个对象转换为Student类型,我们就可以使用Student类型的方法了
Student person1 = (Student) person;
person1.go();
((Student) person).go();
//子类转换为父类,可能会丢失自己本来的一些方法
Student student = new Student();
student.go();
Person person2 = student;
}
}
/*
1.父类引用指向子类的对象
2.把子类转换为父类,向上转型
3.把父类转换为子类,向下转型:强制转换
4.方便方法的调用,减少重复的代码,是代码变得简洁
抽象:封装、继承、多态 抽象类:接口······
*/
java的动态绑定机制
-
当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
package dynamic; public class Test { public static void main(String[] args) { A a = new B();//向上转型 System.out.println(a.sum());//当调用sum()方法时,先找运行类型B类中的sum方法,方法中调用属性遵循就近原则 System.out.println(a.sum1());//当调用sum1()方法时,先找运行类型B 中的sum1()方法,如果没找到, //去父类中找,找到了就调用父类中的,但是sum1()方法中调用了getI()方法,所以还是遵循调用原则,先找运行类型B 中的 //实现了动态绑定 B a1 = (B) a;//向下转型 System.out.println(a1.i); } } class A { public int i = 10; public int sum(){ return i+20; } public int getI(){ return i; } public int sum1(){ return getI()+10; } } class B extends A{ public int i = 20; public int sum(){ return i+20; } public int getI(){ return i; } public int sum1(){ return getI()+10; } }
-
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
import poly.polyarr.Person;
public class Test1 {
public static void main(String[] args) {
Person[] person = new Person[5];
person[0] = new Person("jack",22);
person[1] = new Student("小明",15,60.6);
person[2] = new Student("小李",15,99.9);
person[3] = new Teacher("老李",35,50000);
person[4] = new Student("小盛",28,90000);
for (int i = 0; i < person.length; i++) {
//Person[i]编译类型时Person,运行类型时根据实际情况由JVM来判断
System.out.println(person[i].say());//动态绑定机制
if ( person[i] instanceof Student) {//判断person[i]的运行类型是不是Student
Student student = (Student)person[i];//向下转型
//也可以用 ((Student)person[i]).say();
student.study();
} else if (person[i] instanceof Teacher) {//判断person[i]的运行类型是不是Teacher
Teacher teacher = (Teacher) person[i];//向下转型
teacher.teach();
}else if (person[i] instanceof Person) {
}else {
System.out.println("你的代码有误");
}
}
}
}
package poly.polyarr;
//创建一个Person对象,两个Student对象和两个Teacher对象,统一放在数组中,并调用say方法
public class Person {//父类
private String name;//姓名
private int age;//年龄
public Person(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 String say(){
return "姓名:"+name+" 年龄:"+age;
}
}
class Teacher extends Person{//子类
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String say() {
return super.say()+" 工资:"+salary;
}
public void teach(){
System.out.println("教学生");
}
}
class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
//重写父类的say方法
@Override
public String say() {
return super.say()+" 成绩:"+score;
}
public void study(){
System.out.println("学会儿");
}
}
- 方法定义的形参类型为父类类型,实参类型允许为子类类型
package poly.polyparameter;
import poly.Master;
//测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资
//并在main方法中调用该方法
//测试类中增加一个方法testWork,如果时普通员工,则调用work方法,如果是经理,则调用manage方法
public class PloyParameter {
public static void main(String[] args) {
PloyParameter ployParameter = new PloyParameter();//创建对象方便引用方法
// Manager li = new Manager("li",10000,2000);//创建经理对象
// Ordinary liu = new Ordinary("liu", 5000);//创建普通员工对象
// ployParameter.testWork(liu);
// ployParameter.showEmpAnnual(liu);
// ployParameter.testWork(li);
// ployParameter.showEmpAnnual(li);
Employee[] e = new Employee[2];//用多态数组的方式表示
e[0] = new Manager("威", 10000, 1500);
e[1] = new Ordinary("赞", 5000);
for (int i = 0; i < e.length; i++) {
System.out.println(e[i]);
ployParameter.showEmpAnnual(e[i]);
ployParameter.testWork(e[i]);
}
}
public void showEmpAnnual(Employee e) {//查看年薪
System.out.println(e.getAnnual());
}
public void testWork (Employee e){//用来判定运行类型时那个子类,以此来决定调用那个方法
if (e instanceof Manager) {//条件判断其运行类型是不是Manager
((Manager) e).manage();
} else {
((Ordinary) e).work();
}
}
}
package poly.polyparameter;
//包含姓名和月工资[private]。以及计算年工资getAnnual的方法
//普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法
//普通员工多了work方法,普通员工和经理类要求分别重写getAnnual方法
public class Employee {//父类
private String name;//名字
private double salary;//薪水
public Employee(){}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getAnnual(){
return salary*12;
}
}
package poly.polyparameter;
//普通员工多了work方法
public class Ordinary extends Employee{//普通员工类,子类
public Ordinary(String name, double salary) {
super(name, salary);
}
public void work(){
System.out.println("员工"+getName()+"工作了");
}
@Override
public double getAnnual() {//重写父类的方法
return super.getAnnual();
}
}
package poly.polyparameter;
//经理类多了奖金bonus属性和管理manage方法
public class Manager extends Employee {//经理类,子类
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public void manage(){
System.out.println("经理"+getName()+"进行了管理");
}
public double getAnnual(){
return (getSalary()+bonus)*12;
}
}
Object类详解
equals方法
== 和equals的对比[面试题]
- ==:既可以判断基本类型,又可以判断引用类型
- ==:如果判断基本类型,判断的是值是否相等
- ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一对象
- equals:是Object类中的方法,只能判断引用类型
- 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等
package poly.Equals;
public class EqualsExercise2 {
public static void main(String[] args) {
A a = new A();
a.name = "hshs";
A a1 = new A();
a1.name = "haha";
System.out.println(a == a1);//比较地址,两个地址不同 false
System.out.println(a.name.equals(a1.name));//比叫字符串 true
System.out.println(a.equals(a1));//比较地址,地址不同 false
String s1 = new String("as");
String s2 = new String("as");
System.out.println(s1.equals(s2));//String类中的equals方法被重写过,比较的是字符串
System.out.println(s1 == s2);//引用比较,比较的是地址,地址不同,false
}
}
class A{
public String name;
}
-
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
-
hashCode 的常规协定是:
- 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
- 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
-
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 Java™ 编程语言不需要这种实现技巧。)
-
返回:
此对象的一个哈希码值。
小结:
- 提高具有哈希结构的容器的效率
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!不能完全将哈希是等价于地址
- 后面在集合中,hashCode如果需要的话,也会重写
public class Test {
public static void main(String[] args) {
A a = new A();
B b = new B();
A a1 = a;
System.out.println("a.hasCode()="+a.hashCode());
System.out.println("b.hasCode()="+b.hashCode());
System.out.println("a1.hasCode()="+a1.hashCode());
}
}
public class A {
}
public class B {
}
toString方法
- 基本介绍:
- 默认返回:全类名+@+哈希值的十六进制,子类往往重写toString方法,用于返回对象的属性信息
- 重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式
- 当直接输出一个对象时,toString方法会被默认的调用
package object.toString;
//重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("小李", "视察", 9500);
System.out.println(monster.toString());
System.out.println(monster);//等价于monster.toString(),默认调用toString方法
}
}
class Monster{
private String name;
private String job;
private double salary;//薪水
public Monster(String name, String job, double salary) {
this.name = name;
this.job = job;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
/*重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式
toString源码
1.getClass().getName() 类的全类名(包名+类名)
2.Integer.toHexString(hashCode())将对象的hashCode值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
@Override
public String toString() {
return "Monster {"+
"name:"+name+
" job:"+job+
" salary:"+salary+"}";
}
}
finalize方法
- 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
- 什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该项,在销毁该对象前,会先调用finalize方法
- 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动出发垃圾回收机制
- 在研究开发中,几乎不会运用finalize,更多的时为了应对面试
public class Finalize {
public static void main(String[] args) {
Car car = new Car("凯迪拉克");
car = null;//这个时候,car对象就是一个垃圾了,垃圾回收器就会回收(销毁)对象,在销毁对象前,会调用该对象的finalize方法
//程序员可以在finalize中写自己的业务逻辑代码(是释放资源,数据库链接,或者打开文件......)
//程序员如果不重写finalize方法,那么就会调用Object类中的finalize,即默认处理
//如果程序员重写了finalize,就可以实现自己的逻辑
System.gc();//主动调用垃圾回收器
}
}
class Car{
private String name;
public Car(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//重写finalize
@Override
protected void finalize() throws Throwable {
System.out.println("我们销毁汽车"+name);
System.out.println("释放了某些资源。。。。");
}
}
断点调试
- 在开发中,新手程序员在查找错误时,可以用断点调试,一步一步的看源码执行的过程,从而发现错误所在
- 在断点提示的过程中,时运行状态,以对象的运行类型来执行的
快捷键:
- F7 跳入方法内
- F8逐行执行代码
- shift+F8 跳出方法
- F9直接执行到下一个断点
介绍:
- 断点调试时指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停止,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到除左的代码行即显示错误,停下。进行分析从而找到这个Bug
- 断点调试时程序员必须掌握的湖北不过
- 断点调试也能帮助我们查看java源代码的执行过程,提高程序员的java水平
房屋出租练习
- 代码实现
package houserent.domain;
/***
* House的对象表示一个房屋的信息
*/
public class House {
//编号 房主 电话 地址 月租 出租情况
private int id;
private String name;
private String phone;
private String address;
private int rent;
private String state;
public House(int id, String name, String phone, String address, int rent, String state) {
this.id = id;
this.name = name;
this.phone = phone;
this.address = address;
this.rent = rent;
this.state = state;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getRent() {
return rent;
}
public void setRent(int rent) {
this.rent = rent;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//为了方便输出信息,我们实现toString
//编号 房主 电话 地址 月租 出租情况
@Override
public String toString() {
return id
+"\t\t"+name
+"\t"+phone
+"\t\t"+address
+"\t\t"+rent
+"\t\t"+state
;
}
}
package houserent.service;
import houserent.domain.House;
/*
业务层
定义House[],保存House对象
1.相应HouseView的调用
2.完成对房屋信息的各种操作(增删改查)
*/
public class HouseService {
/*
1.编写一个方法list()
放回所有的房屋信息
定义House[],保存House对象
*/
private House[] houses;//保存House对象
private House[] house1;//当房屋数量超出规定时用于扩容
private int houseNums = 1;//记录当前有多少个房屋信息
private int idCounter = 1;//记录当前的id增长到哪个值
public HouseService(int size){
houses = new House[size];//当创建HouseService对象,指定数组大小
house1 = new House[size+1];
//为了配合测试列表信息,初始化一个House对象
houses[0] = new House(1,"jack","1321312","海边",2000,"未租出");
}
/*
2.编写方法add(House newHouse),把新的house对象加入到houses数组,返回boolean
*/
public boolean add(House newHouse){
//判断是否还可以继续添加(暂时不考虑扩容的问题)
if (houseNums >=houses.length) {//不能再添加
for (int i = 0; i < houses.length; i++) {
house1[i] = houses[i];
}
house1[houses.length] = newHouse;
houses = house1;
}
//把newHouse对象加入到数组,新增加了一个房屋
houses[houseNums++] = newHouse;
//需要设计一个id自增长的机制,然后更新newHouse的id
newHouse.setId(++idCounter);
return true;
}
//3.编写方法del(int delId);完成真正的删除任务
public boolean del(int delId){
//应当先找到删除的房屋信息对应的数组下标
int index = -1;
for (int i = 0; i < houseNums; i++) {
if (delId == houses[i].getId()) {//要删除的房屋id,是数组下标为i的元素
index = i;//使用index记录i
}
}
if (index == -1) {//说明delId再数组中不存在
return false;
}
for (int i = index; i <houseNums-1 ; i++) {
houses[i] = houses[i+1];
}
houses[--houseNums] = null;//把当前存在的房屋信息的最后一个设置为null
return true;
}
//4.编写方法find(int findId);返回House对象。如果没有返回null
public House find(int findId){
//遍历数组
for (int i = 0; i < houseNums; i++) {
if (findId == houses[i].getId()) {
return houses[i];
}
}
return null;
}
//
//list方法,返回houses
public House[] list(){
return houses;
}
}
package houserent.utils;
/**
工具类的作用:
处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/
import java.util.*;
/**
*/
public class Utility {
//静态属性。。。
private static Scanner scanner = new Scanner(System.in);
/**
* 功能:读取键盘输入的一个菜单选项,值:1——5的范围
* @return 1——5
*/
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);//包含一个字符的字符串
c = str.charAt(0);//将字符串转换成字符char类型
if (c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
/**
* 功能:读取键盘输入的一个字符
* @return 一个字符
*/
public static char readChar() {
String str = readKeyBoard(1, false);//就是一个字符
return str.charAt(0);
}
/**
* 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符
* @param defaultValue 指定的默认值
* @return 默认值或输入的字符
*/
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
return (str.length() == 0) ? defaultValue : str.charAt(0);
}
/**
* 功能:读取键盘输入的整型,长度小于2位
* @return 整数
*/
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(10, false);//一个整数,长度<=10位
try {
n = Integer.parseInt(str);//将字符串转换成整数
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数
* @param defaultValue 指定的默认值
* @return 整数或默认值
*/
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(10, true);
if (str.equals("")) {
return defaultValue;
}
//异常处理...
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的指定长度的字符串
* @param limit 限制的长度
* @return 指定长度的字符串
*/
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
/**
* 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串
* @param limit 限制的长度
* @param defaultValue 指定的默认值
* @return 指定长度的字符串
*/
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("")? defaultValue : str;
}
/**
* 功能:读取键盘输入的确认选项,Y或N
* 将小的功能,封装到一个方法中.
* @return Y或N
*/
public static char readConfirmSelection() {
System.out.println("请输入你的选择(Y/N): 请小心选择");
char c;
for (; ; ) {//无限循环
//在这里,将接受到字符,转成了大写字母
//y => Y n=>N
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
/**
* 功能: 读取一个字符串
* @param limit 读取的长度
* @param blankReturn 如果为true ,表示 可以读空字符串。
* 如果为false表示 不能读空字符串。
*
* 如果输入为空,或者输入大于limit的长度,就会提示重新输入。
* @return
*/
private static String readKeyBoard(int limit, boolean blankReturn) {
//定义了字符串
String line = "";
//scanner.hasNextLine() 判断有没有下一行
while (scanner.hasNextLine()) {
line = scanner.nextLine();//读取这一行
//如果line.length=0, 即用户没有输入任何内容,直接回车
if (line.length() == 0) {
if (blankReturn) return line;//如果blankReturn=true,可以返回空串
else continue; //如果blankReturn=false,不接受空串,必须输入内容
}
//如果用户输入的内容大于了 limit,就提示重写输入
//如果用户如的内容 >0 <= limit ,我就接受
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
package houserent.view;
/*
界面类
1.显示界面
2.接受用户的输入
3.调用HouseService完成对房屋信息的各种操作
*/
import houserent.domain.House;
import houserent.service.HouseService;
import houserent.utils.Utility;
import java.util.Scanner;
public class HouseView {
Scanner scanner = new Scanner(System.in);
private boolean loop = true;
private char key = ' ';
private HouseService houseService = new HouseService(10);
/*
3.编写addHouse()接受输入
创建House对象,调用add方法
*/
public void addHouse(){
System.out.println("===========添加房屋===========");
System.out.print("姓名:");
String name = Utility.readString(8);
System.out.print("电话:");
String phone = Utility.readString(12);
System.out.print("地址:");
String address = Utility.readString(16);
System.out.print("月租:");
int rent = Utility.readInt();
System.out.print("状态:");
String state = Utility.readString(3);
//创建一个新的House对象,注意id 是系统分配的用户不能自主输入
House newHouse = new House(0, name, phone, address, rent, state);
if (houseService.add(newHouse)) {
System.out.println("=====添加房屋成功=====");
}else {
System.out.println("=====添加房屋失败=====");
}
}
//4.编写delHouse()接收用户输入id,调用HouseService的del方法
// 删除房屋信息
public void delHouse(){
System.out.println("===========删除房屋===========");
System.out.println("请选择待删除的房屋编号(-1退出):");
int delId = Utility.readInt();
if (delId == -1) {
System.out.println("===========放弃删除房屋===========");
return;
}
char choice = Utility.readConfirmSelection();//注意本方法本身就有循环判断的逻辑,必须输入y/n
if (choice == 'Y') {
if (houseService.del(delId)) {
System.out.println("======删除房屋信息成功========");
}else {
System.out.println("======房屋编号不存在,删除失败");
}
}else {
System.out.println("=========放弃删除房屋信息=======");
}
}
//2.编写listHouse()显示房屋列表
public void listHouses(){
System.out.println("===============房屋列表==============");
System.out.println("编号\t\t房主\t\t电话\t\t\t地址\t\t月租\t\t出租情况(已出租/未出租)");
House[] houses = houseService.list();//得到所有的房屋信息
for (int i = 0; i < houses.length; i++) {
if (houses[i] == null) {
break;
}
System.out.println(houses[i]);
}
System.out.println("========房屋列表显示完毕=========");
}
//完成退出确认
public void exit(){
char c = Utility.readConfirmSelection();
if (c == 'Y') {
loop = false;
}
}
//5.表现捏findHouse();界面,接受输入的id
public void findHouse(){
System.out.println("=======查找房屋信息==========");
System.out.println("请输入要查找的id:");
int findId = Utility.readInt();
House house = houseService.find(findId);
if (house != null) {
System.out.println(house);
}else {
System.out.println("============查找的房屋信息不存在===========");
}
}
//6.编写updateHouse();接收输入id根据id房屋修改信息
public void updateHouse(){
System.out.println("==========修改房屋信息==========");
System.out.println("请选择待修改房屋的编号(-1表示退出):");
int updateId = Utility.readInt();
if (updateId == -1) {
System.out.println("==========放弃修改房屋信息==========");
return;
}
//根据输入的updateId查找对象
// 返回的是引用类型(就是数组的元素)
//所以在后面对house.setXxx(),就会修改HouseService中数组的元素
House house =houseService.find(updateId);
if (house == null) {
System.out.println("=========修改房屋的编号不存在===========");
return;
}
System.out.print("姓名("+house.getName()+"):");
String name = Utility.readString(8," ");//如果用户直接回车表示不修改信息,默认""
if (!"".equals(name)) {//如果输入的字符串是空的则不修改,如果不是空的修改
house.setName(name);
}
System.out.print("电话("+house.getPhone()+"):");
String phone = Utility.readString(12,"");//如果用户直接回车表示不修改信息,默认""
if (!"".equals(phone)) {//如果输入的字符串是空的则不修改,如果不是空的修改
house.setPhone(phone);
}
System.out.print("地址("+house.getAddress()+"):");
String address = Utility.readString(18,"");//如果用户直接回车表示不修改信息,默认""
if (!"".equals(address)) {//如果输入的字符串是空的则不修改,如果不是空的修改
house.setAddress(address);
}
System.out.print("租金("+house.getRent()+"):");
int rent = Utility.readInt(-1);
if (rent != -1) {//如果输入的租金是-1的则不修改,如果不是-1s的修改
house.setRent(rent);
}
System.out.print("状态("+house.getState()+"):");
String state = Utility.readString(3,"");//如果用户直接回车表示不修改信息,默认""
if (!"".equals(state)) {//如果输入的字符串是空的则不修改,如果不是空的修改
house.setState(state);
}
System.out.println("=======修改房屋信息完毕=========");
}
//1.编写delHouse()界面,接收用户输入id
//编写mainMenu方法,显示主菜单
public void mainMenu(){
do {
System.out.println("---------------------房屋出租系统---------------------------");
System.out.println("\t\t\t\t\t1.新 增 房 源");
System.out.println("\t\t\t\t\t2.查 找 房 源");
System.out.println("\t\t\t\t\t3.删 除 房 屋");
System.out.println("\t\t\t\t\t4.修 改 房 屋 信 息");
System.out.println("\t\t\t\t\t5.房 屋 列 表");
System.out.println("\t\t\t\t\t6.退 出");
System.out.println("请选择1-6:");
key = Utility.readChar();
switch (key){
case '1':
addHouse();
break;
case '2':
findHouse();
break;
case '3':
delHouse();
break;
case '4':
updateHouse();
break;
case '5':
listHouses();
break;
case '6':
exit();
break;
default:
System.out.println("选择有误,请重新选!");
}
}while (loop);
System.out.println("退出系统");
}
}
package houserent;
import houserent.view.HouseView;
public class HouseRentApp {
public static void main(String[] args) {
new HouseView().mainMenu();
}
}
类变量和方法
- 静态变量被同一个对象所有的对象共享
- jdk7或jdk8之前,静态变量的地址在方法池
- jdk7之后,静态变量的地址在堆中
- 不论在哪不影响对静态变量的影响
注意:当类加载时,会在堆中生成一个class对象静态变量会保存在其尾部,在类加载的时候就生成了,没有创建对象实例也可以通过类名.类变量名来访问
什么是类变量
- 类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也都是同一个变量。
package com.shedu.static_;
public class ChildGame {
public static void main(String[] args) {
Child jack = new Child("jack");
jack.join();
jack.count++;
Child mack = new Child("mack");
mack.join();
mack.count++;
Child luce = new Child("luce");
luce.join();
luce.count++;
}
}
class Child{
private String name;
//定义一个变量count,是一个类变量(静态变量)static静态
//该变量最大的特点就是会被Child类的所有对象实例共享
public static int count = 0;
public Child(String name){
this.name = name;
}
public void join(){
System.out.println(name+"加入了游戏......");
System.out.println("总共有"+count+"个人在玩游戏!");
}
}
内存分析
如何定义类变量
- 定义语法:
- 访问修饰符 static 数据类型 变量名;(推荐)
- static 访问修饰符 数据类型 变量名;
如何访问类变量
- 类名.类变量名
- 对象名.类变量名
- 静态变量的访问修饰符的访问权限和范围和普通属性是一样的
注意事项和使用细节
-
什么时候需要用类变量:
当我们需要让某个类的所有对昂都共享一个变量时,就可以考虑使用类变量(静态变量)
-
类变量与实例变量的区别:
类变量是该类的所有对象共享的,而实例变量时每个对象独享的
-
加上sytatic成为类变量或静态变量,否则成为实例变量/普通变量/非静态变量
-
类变量可以通过类名.类变量名或者对象名.类变量名来访问,单java设计者推荐使用类名.方法名的方式访问,前提是必须满足访问权限和范围
-
实例变量不能通过 类名.类变量名 方式访问
-
类变量时在类加载时就初始化了,也就是说,即使你没有船舰对象,只有类加载了,就可以使用类变量
-
类变量的生命周期时随类的加载开始,随着雷孝王而销毁
如何定义类方法
- 类方法也叫静态方法
- 定义语法:
- 访问修饰符 static 数据类型 方法名(){};(推荐)
- static 访问修饰符 数据类型 方法名(){};
如何访问类方法
- 类名.类方法名
- 对象名.类方法名
- 静态方法的访问修饰符的访问权限和范围和普通属性是一样的
类方法的经典使用场景
- 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
- 比如:工具类中的方法
类方法使用注意事项和细节讨论
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
- 类方法中无this的参数
- 普通方法中隐含着this的参数
- 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
小结
- 静态方法只能访问惊天成员
- 非静态方法,可以访问所有的成员
- 在编写代码时,仍然要遵守访问权限规则
main方法
main方法的形式:public static void main(String[] args) {}
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须时public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须时static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- Java执行的程序参数1 参数2 参数3
特别提示:
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
- 不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
代码块
基本介绍:
- 代码块又称为初始化块,属于类中的成员(是类的一部分),类似于方法,讲逻辑语句封装在方法体中,通过{}包围起来
- 但是和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象是隐式调用
基本语法
[修饰符]{
代码
}
注意:
- 修饰符可选,要写的化,只能写static
- 嗲马快必须分为两类,使用static修饰的叫静态代码块,没有static修饰的叫普通代码块
- 逻辑语句可以为任何逻辑局(输入,输出,方法调用,循环,判断)
- ;可以写上,也可以省略
好处
- 相当于另外一种形式的构造器(对构造器的补充机制),可以作初始化的操作
- 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
使用注意事项和细节讨论
-
static代码块也叫静态代码块,作用就是堆类进行初始化,而且它随着类的加载而执行,并且只执行一次。普通代码块,每创建一个对象,就执行一次
-
类什么时候被加载
- 创建对象实例时
- 创建子类对象实例,父类也会被加载,父类的要优先于子类
- 使用类的静态成员(静态方法,静态属性)时
-
普通的代码块,在创建对象实例时,会被饮食的调用。被创建一次就会调用一次,如果知识使用类的静态成员时,普通代码块并不会执行
-
创建一个对象时,在一个类 调用顺序是:(重难点):
- 调用静态代码块和静态属性初始化(注意:静态代码块个静态初始化调用的优先级一样,如果有多个静态代码块和多个静态代码初始化,则按他们定义顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用
- 调用构造方法
-
构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
-
我么看一下创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 子类的构造方法
-
静态代码块只能直接调用静态成员,普通代码块可以调用任意成员
-
package com.shedu.static_.codeblock; public class CodeBlockDetails { public static void main(String[] args) { CC cc = new CC(1); } } class AA{ public static int a = a(); public int aa = aa(); static { System.out.println("AA()的静态代码块"); } { System.out.println("AA的普代"); } public static int a(){ System.out.println("AA的静态方法"); return AA.a; } public int aa(){ System.out.println("AA的普通方法"); return this.aa; } public AA(int a) { System.out.println("AA构造器"); } } class BB extends AA{ public static int b = b(); public int bb = bb(); static { System.out.println("bb()的静态代码块"); } { System.out.println("bb的普代"); } public int bb(){ System.out.println("bb的普通方法"); return this.bb; } public static int b(){ System.out.println("BB的静态方法"); return BB.b; } public BB(int a) { super(a); System.out.println("bb构造器"); } } class CC extends BB{ public static int c = c(); public int cc = cc(); static { System.out.println("cc()的静态代码块"); } { System.out.println("cc的普代"); } public int cc(){ System.out.println("cc的普通方法"); return this.cc; } public static int c(){ System.out.println("CC的静态方法"); return CC.c; } public CC(int a) { super(a); System.out.println("cc构造器"); } } /*输出结果:AA的静态方法 AA()的静态代码块 BB的静态方法 bb()的静态代码块 CC的静态方法 cc()的静态代码块 AA的普通方法 AA的普代 AA构造器 bb的普通方法 bb的普代 bb构造器 cc的普通方法 cc的普代 cc构造器 */
单例设计模式
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式实在大量的时间中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己在思考和摸索
什么是单例模式
单例(单个的实例)
-
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
-
单例模式有两种方式:
- 饿汉式(静态常量/静态代码块)
- 懒汉式(线程不安全/线程安全,同步方法/线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
单例模式应用案例
步骤如下:
- 构造器私有化 =>防止直接 new,导致多个实例
- 类的内部创建对象
- 向外暴漏一个静态的方法 getInstance
- 代码实现
枚举
-
饿汉式和懒汉式区别:
- 饿汉式是在类内部一开始初始化时就创建好实例,而懒汉式是需要用的时候才去创建实例
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
- 饿汉式存在浪费资源的可能,当程序员一个对象实例都没有使用,那么饿汉式创建表的对象就浪费了,懒汉式是使用时才创建,不存在这个问题
- 在JavaSE标准类中,Java.lang.Runtime就是经典的单例模式
-
饿汉式(静态常量/静态代码块)
在开发中推荐使用,尤其是该类的实例会用到,避免了造成内存的浪费,如果该类的实例不用,则会造成内存的浪费。
-
//饿汉式 public class SingleTon01 { public static void main(String[] args) { //通过方法可以获取对象 GirlFriend instance = GirlFriend.getInstance(); System.out.println(instance.toString()); } } //有一个类,GirlFriend //只能有一个女朋友 class GirlFriend{ private String name; //为了能够在静态方法中,返回gf对象,需要将其修饰为static对象 private static GirlFriend gf = new GirlFriend("12"); //如何保障我们只能创建一个GirlFriend对象 //步骤(饿汉式) //1. 将构造器私有化 //2.在类的内部直接创建 //3.提供一个公共的static方法,返回gf对象 private GirlFriend(String name) { this.name = name; } public static GirlFriend getInstance(){ return gf; } @Override public String toString() { return "name:"+this.name; } }
-
饿汉式(静态代码块)
-
*/ public class Singleton{ private static Singleton singleton = new Singleton();//饿汉式,初始化时就创建好了实例 //代码块[使用了static代码块-->饿汉式(静态代码块)] static{ singleton = new Singleton(); } private Singleton(){}//构造器私有化,防止new,导致多个实例 public static Singleton getInstance(){//向外暴露一个静态的公共方法 getInstance return singleton; } }
-
饿汉式(静态常量) 和 饿汉式(静态代码块) 的优点、缺点:相同
-
优点:实现较为简单,在类加载时就完成了实例化,避免了多线程同步问题
-
缺点:在类加载时就完成了实例化(使类加载的情况有很多种,不一定是调用getInstance()方法使类进行加载的),没有达到懒加载的效果。如果程序从始至终未用到该实例,则造成了空间浪费
-
懒汉式(线程不安全/ 线程安全,同步方法 / 线程安全,同步代码块)
-
懒汉式(线程不安全)
package com.shedu.single; /** * 懒汉式的单例模式 */ public class SingleTen02 { public static void main(String[] args) { Cat instance = Cat.getInstance(); System.out.println(instance); } } //希望在程序运行过程中,只能创建一个对象 class Cat{ private String name; private static Cat cat; //步骤: //1.构造器私有化 //2.定义一个static静态属性对象 //3.提供一个public的static方法,可以返回一个Cat对象 //4.懒汉式,只有当用户使用getInstance时,才返回cat对象, //后面再次调用时,会返回上次创建的cat对象,从而保证了单例 private Cat(String name) { this.name = name; } public static Cat getInstance(){ if (cat == null) {//如果没有创建cat对象 cat = new Cat("小猫"); } return cat; } @Override public String toString() { return "Cat{" + "name='" + name + '\'' + '}'; } }
-
-
懒汉式(线程不安全) 优缺点:起到懒加载的效果,但是只适合在单线程下使用(开发中不推荐使用)
-
线程不安全原因:如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
-
懒汉式(线程安全,同步方法)
public class Singleton{ private Singleton(){}//构造器私有化,防止new,导致多个实例 private static Singleton singleton; //同步方法,synchronized直接加在方法上 public static synchronized Singleton getInstance(){//向外暴露一个静态的公共方法 getInstance if(singleton == null){ singleton = new Singleton(); } return singleton; } }
-
-
懒汉式(线程安全,同步方法) 优缺点:起到懒加载的效果,线程安全,但是调用效率低(开发中不推荐使用)
-
懒汉式(线程安全,同步代码块)
public class Singleton{
private Singleton(){}//构造器私有化,防止new,导致多个实例
private static Singleton singleton;
public static Singleton getInstance(){//向外暴露一个静态的公共方法 getInstance
if(singleton == null){
//同步代码块,synchronized是单独作为代码块使用
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
-
懒汉式(线程安全,同步代码块) 优缺点:起到懒加载的效果,但是只适合在单线程下使用(开发中不推荐使用)
- 线程不安全原因:和 懒汉式(线程不安全)一样。
-
双重检查
public class Singleton{
private Singleton(){}//构造器私有化,防止new,导致多个实例
private static volatile Singleton singleton;
public static Singleton getInstance(){//向外暴露一个静态的公共方法 getInstance
//第一层检查
if(singleton == null){
//同步代码块
synchronized (Singleton.class){
//第二层检查
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查优缺点:解决了同步代码块方式的线程安全问题。
其实就是上面的 懒汉式(线程安全,同步代码块)的优化改良版。
-
静态内部类
public class Singleton{ private Singleton(){}//构造器私有化,防止new,导致多个实例 //静态内部类,在其内部以静态常量的方式实例化对象 private static class SingletonInstance{ private static final Singleton singleton = new Singleton();//常量静态属性,实例化对象[初始化] } public static Singleton getInstance(){//向外暴露一个静态的公共方法 getInstance return SingletonInstance.singleton; } }
- 静态内部类优缺点:(利用了jvm的两个特点,起到了懒加载、线程安全的作用)
懒加载:利用了jvm装载的特点:当外部类加载的时候,内部静态类不会被加载,从而保证了懒加载。
线程安全:当类在进行初始化的时候,别的线程是无法进入的。通过类的静态属性只会在第一次加载类的时候初始化,保证了线程安全。
1.当外部类 Singleton被装载时,静态内部类 SingletonInstance不会被立即装载,实现懒加载
2.当外部类 Singleton调用getInstance()时,静态内部类 SingletonInstance只被装载一次,在初始化静态内部类SingletonInstance的静态常量属性 singleton,保证了线程安全。
- 静态内部类优缺点:(利用了jvm的两个特点,起到了懒加载、线程安全的作用)
-
枚举方式
enum Singleton{ INSTANCE; public void method(){ // 操作方法 } }
-
枚举方式优缺点: 线程安全,效率高,还可防止反序列化重新创建新的对象.
四、单例的使用场景
需要频繁的进行创建和销毁的对象、
创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
final关键字
final中文意思:最终的最后的;
final可以修饰类,属性,方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用final:
- 当不希望类被继承时,可以使用final修饰
- 当不希望父类的某个方法被子类覆盖/重写(override)时,可以使用final关键字修饰
- 当不希望类的某个属性的值被修改,可以用final修饰
- 当不希望某个局部变量被修改,可以使用final修饰
package com.shedu.final_;
public class Final01 {
public static void main(String[] args) {
// E e = new E();
// e.t = 0.03;
// F f = new F();
// f.cry();
}
}
//如果我们要求A类不能被其他类继承
//可以使用final修饰A类
final class A{}
//class B extends A{}
class C{
//如果我们要求hi不能被子类重写
//可以使用final修饰 hi方法
public final void hi(){
}
}
class D extends C{
// @Override
// public void hi() {
// super.hi();
// }
}
//当不希望类的某个属性的值被修改,可以用final修饰
class E{
public final double t = 0.08;
}
//当不希望某个局部变量被修改,可以使用final修饰
class F{
// public void cry() {
//这时n也被称为局部常量
// final double n = 0.01; //final修饰后无法被修改
// n = 0.02;
// System.out.println("n=" + n);
// }
}
使用注意事项和细节讨论
- final修饰的属性又叫常量,一般用XX_XX_XX来命名
- final修饰的属性在定义时,必须赋初值,并且不能再修改,赋值可以在如下位置之一(选择一个位置赋初值即可):
- 定义时:如public final double TAX_RATE = 0.08;
- 在构造器时
- 在代码块中
- 如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 在静态代码块,不能在构造器中赋值
- final类不能继承,到那时可以实例化对象
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
- 如果一个类已经是final类了,就没有必要再将方法修饰成final方法
- final不能修饰构造方法(构造器)
- final和static往往搭配使用,效率更高,不会导致类的加载,底层翻译器优化处理
- 包装类(Integer,Double ,Float,Boolean等都是final),String也是final类
抽象类
介绍
-
用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{}
-
用abstract关键字来修饰一个方法时,这份方法就是抽象方法,访问修饰符abstract 返回类型 方法名(参数列表);//没有方法体
-
抽象类的价值更多作用时在于设计,是设计者设计好后,让子类继承并实现抽象类()
-
抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
使用的注意事项和细节讨论
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法
- 一旦包含了abstract方法,则这个类必须声明为abstract
- abstract只能修饰类和方法,不能修饰属性和其它的
- 抽象类可以有人以成员【因为抽象类还是类】,比如非抽象方法,构造器,静态属性等
- 抽象方法不能有主体,即不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非她自己也声明为abstract类
- 抽象方法不能使用private,final和static来修饰,因为这些关键字都是和重写相违背的
- static修饰的方法是静态方法,其可以直接被类所调用,需要在子类或实现类中去编写完整的方法处理逻辑后才能使用
- final修饰的类和方法不能被继承,所以无法重写和实现
- private修饰的方法不能被子类重写
练习
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法work()。对于Manager类来说,它既是员工,还具有奖金(bonus)的属性,请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必须的方法进行属性访问,实现work(),提示"经理/普通员工 名字 工作中…"
package com.shedu.abstract_;
public class AbstractExercise01 {
public static void main(String[] args) {
CommonEmployee commonEmployee = new CommonEmployee("ZHENG", 21, 1200);
System.out.println(commonEmployee.work());
Manager manager = new Manager("在栈中", 01, 1200,1000);
System.out.println(manager.work());
}
}
abstract class Employee{
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public abstract String work();//抽象方法
}
class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public String work() {
return "普通员工:"+getName()+"正在工作中";
}//重写抽象方法
}
class Manager extends Employee{
private double bonus;//奖金
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public String work() {
return "经理:"+getName()+"正在工作中";
}//重新给抽象方法
}
抽象类最佳实践–模板设计模式
- 最佳实践
- 需求
- 有多个类,可以完成不同的任务
- 要求统计得到各自完成任务的时间
- 实现代码
- 需求
package com.shedu.abstract_;
public class TestTemplate {//模板设计模式
public static void main(String[] args) {
AA aa = new AA();
aa.calculate();
}
}
abstract class ABC{
public abstract void job();//抽象方法
public void calculate(){//调用job()来完成完成任务所用时间的计算
//获得开始的时间
long start = System.currentTimeMillis();
job();//动态绑定机制
//获得结束的时间
long end = System.currentTimeMillis();
System.out.println("时间="+(end-start));
}
}
class AA extends ABC{
@Override
public void job() {//重写了ABC的job()方法
long num = 0;
for (int i = 0; i < 1000000; i++) {
num+=i;
}
}
}
class BB extends ABC{//重写了ABC的job()方法
@Override
public void job() {
long num = 1;
for (int i = 0; i < 1000000; i++) {
num*=i;
}
}
}
异常和处理
异常
- 软件程序在运行过程中,非常可能遇到异常问题
- 异常指程序运行中出现的不期而至的各种情况 如:文件找不到、网络连接失败、非法参数等
- 异常发生在程序运行期间,他影响了正常的程序执行流程
简单分类
- 检查性异常:最具代表性的检查性异常时用户错误并或问题引起的异常,这是程序员无法预见的。例如:打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 隐形是异常:运行时异常可能是被程序员避免的异常。与检查性异常相反,运行时异常可以在忽略是被忽略。
- 错误:错误不是异常,二十脱离程序员控制的问题。错误在代码中通常被忽略。例如:当栈溢出时,一个错误就打胜了,他们在编译也检查不到的
异常体系结构
- Java可以把异常当作对象来处理,并定义一个基类Java.lang.Theowable作为所有异常的超类
- 在Java API 中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception
Error
- Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关
- Java虚拟机运行错误,当JVM不在又继续执行操作所需要的内存资源是,将出现OutOfMemoryError.这些异常发生时,Java虚拟机(JVM)一般会选择线程终止
- 还有发生子啊虚拟机试图执行应用是,如类定义错误(NoClassDeFoundError)、链接错误(LinkageError)这些错误是不可查的,因为他们在应用程序的控制和处理能力之外,并且绝大多数时程序运行时不允许出现的状况
Exception
-
在Exception分支中有一个重要的子类RuntimeException(运行时异常)
- ArrayIndexOutOfBoundsException(数组下标越界)
- NullPointerException(空指针异常)
- ArithmeticException(算数异常)
- MissingResourceException(丢失资源)
- ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
- 哲学异常一般是有程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
- Error和Exception的区别:Error通常是灾难性的致命的错位于,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常
public class Demo1 { public static void main(String[] args) { int a = 1; int b = 0; //假设想要捕获多个异常,要从小到大捕获,上面写小的 try{//try监控区域 System.out.println(a/b); }catch (ArithmeticException e){//catch(想要捕获的异常类型) 捕获异常 System.out.println("不对吧"); }finally {//处理善后工作,无论怎样都会运行 System.out.println("finally"); //finally可以不要,主要在IO流和资源的关闭中 } } }
异常处理机制
- 抛出异常
- 捕获异常
- 异常处理五个关键字
- try,catch finally throw(手动抛出异常) throws(方法抛出异常)
public class Demo2 {
public static void main(String[] args) {
int a = 1;
int b = 0;
//Ctrl+Alt+T 快捷键
try {
System.out.println(a/b);
} catch (Exception e) {
throw new RuntimeException(e);//打印错误的栈信息
} finally {
System.out.println("finally");
}
}
}
public class Demo3 {
public static void main(String[] args) {
try {
new Demo3().multiply(1,0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//假设这个方法中,处理不了这个异常,方法商抛出异常
public void multiply(int a,int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException();//主动的抛出异常,一般在方法中使用
}else {
System.out.println(a/b);}
}
}
自定义异常
- 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需要继承Exception类即可。
- 在程序中使用自行定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类
- 在方法中通过throw关键字抛出异常对象
- 如果当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
- 再出现异常方法的调用者中捕捉并处理异常
经验总结
- 处理运行异常时,采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch(Exception)来处理可能会被李楼的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切记只是简单地调用printStackTrace()去打印输出
- 具体如何处理一行,要根据不同的业务需求和异常类型去决定
- 尽量添加finally愉快去释放占用的资源