这阵子主要学习了Java的面向对象。面向对象是相对于面向过程来讲的,从面向过程到面向对象,是程序员思想上从执行者到指挥者的转变。面向对象强调的是直接以现实中的事物,也就是对象,为中心来思考,认识问题,并根据它们各自的本质特点(共性),把它们抽象为java中的类,这样的方式与现实世界有着更好的映射,而且也更符合人类的思维习惯。
想学好面向对象,光靠看听是远远不够的,一个合格的程序员同时也是一个辛勤的耕耘者,一定要多写代码,只有在练习的过程中,才会去想这个地方怎么做,用什么做,怎么优化,遇到问题是不可避免的,但也是问题让我们对于每一个问题背后的知识点掌握的更加深刻。接下来就结合具体的例子说一说我学习中遇到的知识点。
我们现在有一个 Person类,它有名字和年龄两个属性,还有一个可以输出姓名年龄的say方法。下边这个代码将会输出什么?
new Person().name = "张三";
new Person().age = 18;
new Person().say();
// 这三个是毫无关系的三个对象
它不会输出 张三 和 18,而是输出Person类的默认值。因为,每一次new都相当于在堆内存中新建一个新的对象,而这个对象如果没有指向它的引用,它最多可以使用一次,这就是匿名对象了,可以类比匿名信,只能和写信的人存在这一次交流,然后你就找不到他了。所以,前两行的代码,在程序执行到第三行的时候就已经找不到了,所以say输出的就是默认值。
静态修饰的方法,被调用时,有可能对象还未创建。
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访
问。并且不会因为对象的多次创建, 而在内存中建立多份数据。
相关使用:static 可以用来修饰 这个类都具有同一个值的属性,这样方便直接修改。
在访问时: 静态不能访问非静态 , 非静态可以访问静态 !因为静态加载完毕的时候,非静态可能还没有加载到内存中。
静态代码块只会在类加载时执行一次,而构造代码块在每次创建对象时都会执行,且在构造方法之前。
构造方法 与 构造代码块 以及 静态代码块的执行顺序:
静态代码块 --> 构造代码块 --> 构造方法
main()方法是程序的入口,程序从这里开始执行:
public static void main(String args[])
以上的各个参数的含义如下:
· public:表示公共的内容,可以被所有操作所调用
· static:表示方法是静态的,可以由类名称直接调用。java StaticDemo09
· void:表示没有任何的返回值操作
· main:系统规定好的方法名称。如果main写错了或没有,会报错:NoSuchMethodError: main
· String[] args:字符串数组,接收参数的
演示:测试这段代码
public class Test {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
String[] args 接收命令参数,什么也不输入,就什么也不输出,输入数字什么的,要用空格隔开,接收进来的就是一个字符串数组。
对于本来就有空格的字符串,要用" "括起来,才能输出你想要的:
java只有单继承。
内存图:
super 保存父类地址,所以可以用它调用父类的东西,因为它就相当于父类的引用。
回顾了构造器重载,以及使用this来减少代码的重复,使用this调用另一个重载的构造器只能在构造器中使用,而
且必须作为构造器执行体的第一条语句。
如图,这种情况,我们就可以使用this调用构造器A,而不用在B,C,D中都写上重复的代码了。使用this的好处
是,如果有一天我们需要对构造器A中的代码进行修改时,如果B,C,D甚至更多的构造器中不是用的this,而是
和A一样的代码,也需要修改,那么工程量就很大了,用this的话,就只需要修改A中代码即可。这样可以充分的利
用每一段代码,既可以让程序简洁,也降低了维护成本。
面试题:
Java中重写(Override)与重载(Overload)的区别
1、发生的位置
重载:一个类中
重写:子父类中
2.参数列表限制
重载:必须不同的
重写:必须相同的
3、返回值类型
重载:与返回值类型无关
重写:返回值类型必须一致
4、访问权限:
重载:与访问权限无关
重写:子类的方法权限必须不能小于父类的方法权限
5、异常处理:
重载:于异常无关
重写:异常范围可以更小,但是不能抛出新的异常。
抽象类就像一个大纲。抽象,顾名思义,就是不够具体,也就是说,只有一个大概。那么抽象类和抽象方法,也就是这么一个意思,我们知道大概要有这么个东西,但是呢,没办法在当时就立马完全的把所有细节搞定,因此先写个抽象类,抽象方法,类似于论文的大纲,大概那么个东西,后期可能还会修改,把它具体化。
在抽象类的使用中有几个原则:
一些容易出错的概念:
1、 抽象类能否使用final声明?
不能,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。
2、 抽象类能否有构造方法?
能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默
认是无参的),之后再调用子类自己的构造方法。
在java中,异常可以两类:
1. try-catch-finally 中哪个部分可以省略?
答: catch和finally可以省略其中一个 , catch和finally不能同时省略
注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也不会这样去写代码.
2. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答:finally中的代码会执行
详解:
执行流程:
1. 先计算返回值, 并将返回值存储起来, 等待返回
2. 执行finally代码块
3. 将之前存储的返回值, 返回出去;
需注意:
1. 返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不会改变
2. finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或catch中的值
3. 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出 JVM:System.exit(0);
递归的一般要求:
递归的应用很广泛,解决问题的思路也很符合我们人类的思维习惯,就是不断的简化问题,直到我们能够直接解决。对于树结构来说,递归遍历是经常性的操作。
递归可以解决的一些问题:斐波那契数列,汉诺塔问题等等。
一般来说,采用递归的时候,最好画出递归树,这样便于观察是否有大量的冗余计算。
递归的格式决定了它会占用大量的栈内存,因此能不用就不用。
案例要求:
快递管理案例:
1.管理员
- 快递录入
- 柜子位置(系统产生,不能重复)
- 快递单号(输入)
- 快递公司(输入)
- 6位取件码(系统产生,不能重复)
- 删除快递(根据单号)
- 修改快递(根据单号)
- 查看所有快递(遍历)
2.普通用户
- 取快递:输入取件码:显示快递的信息和在哪个柜子中,从柜子中移除这个快递
我的代码描述:
View 视图类,主要负责在控制台打印显示提示
Express 快递类,主要定义快递
ExpressData 快递信息类,主要负责对快递的操作
ExpressManagement 主要负责细节逻辑调度
AdministratorClient 管理员客户端,主要用于管理员的各项操作
UserClient 用户客户端,负责取件操作
Main 负责整体逻辑
Main.java
public class Main {
public static void main(String[] args) {
// 创建管理实例
ExpressManagement expressManagement = new ExpressManagement();
// 初始化
expressManagement.initial();
// 管理
expressManagement.management();
}
}
ExpressManagement.java
public class ExpressManagement {
// 视图
View v;
// 快递柜
ExpressData expressData;
// 用户类
UserClient user;
// 管理员类
AdministratorClient administrator;
/**
* 初始化视图和快递柜
*/
public void initial(){
v = new View();
expressData = new ExpressData();
// 欢迎视图
v.welcome();
}
/**
* 快递管理
*/
public void management(){
w:while(true){
int IDCommand = v.identityView();
switch (IDCommand){
// 管理
case 1: {
// 得到身份验证视图返回的验证信息
String[] messages = v.authenticationView();
// 账号
String account = messages[0];
// 密码
String password = messages[1];
// 初始化管理员客户端
administrator = new AdministratorClient(v, expressData);
// 验证
if (administrator.authentication(account, password)) {
v.authenticationSuccess();
// 管理快递
administrator.management();
} else {
v.authenticationFail();
}
break;
}
// 取件
case 2: {
// 用户取件客户端
user = new UserClient(v, expressData);
// 取件
user.getExpress();
break;
}
// 退出
case 0 :
break w;
}
}
// 拜拜视图
v.bye();
}
}
ExpressData.java
import java.util.Arrays;
import java.util.Random;
/**
* 快递数据类,主要负责快递数据的存入,删除,更改
*/
public class ExpressData {
// 快递柜大小为 10 * 10
private final Express[][] expresses = new Express[10][10];
// 定义一个 count 用来记录存储了多少快递
private int count = 0;
// 实例化一个随机器,用来随机生成快递存储位置和取件码
private final Random random = new Random();
public ExpressData() {}
/**
* 用来往快递柜添加快递
* @param express:快递
* @return 是否添加成功
*/
public boolean add(Express express){
// 快递满了就直接返回
if (count == 100){
return false;
}
int row;
int column;
// 随机生成快递存储位置,一定有位置
while(true){
row = random.nextInt(10);
column = random.nextInt(10);
// 判断随机生成的位置是否有快递
if(expresses[row][column] == null){
break;
}
}
// 每放入一个快递,都应该生成一个取件码,一定要注意要在生成取件码之后,再把快递存入
int code = generateCode();
express.setCode(code);
expresses[row][column] = express;
express.setRow(row+1);
express.setColumn(column+1);
count++;
return true;
}
/**
* 生成不重复的取件码
* @return 取件码
*/
private int generateCode() {
// 取件码生成和去重
while (true){
// 取件码范围 100000 - 999999;
int code = random.nextInt(900000) + 100000;
// 根据生成的取件码寻找快递
Express express = findExpressByCode(code);
// 如果不存在这个快递,说明取件码不重复
if (express == null){
return code;
}
}
}
/**
* 删除快递
* @param express:快递
* @return 是否删除成功
*/
public boolean delete(Express express){
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
// express 是引用类型,需要用equals
if (express.equals(expresses[i][j])){
// 找到之后,把它删除,即变成null
expresses[i][j] = null;
count--;
return true;
}
}
}
return false;
}
/**
* 更新快递柜中快递,删除要修改的,增加新的
* @param oldExpress 要修改的快递
* @param newExpress 修改过的快递
*/
public void update(Express oldExpress, Express newExpress){
delete(oldExpress);
add(newExpress);
}
/**
* 返回所有快递
* @return 所有快递
*/
public Express[][] findAll(){
return count > 0? expresses:null;
}
/**
* 根据取件码寻找快递
* @param code 取件码
* @return 快递
*/
public Express findExpressByCode(int code){
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
// code是基本类型int,可以用==比较
if (expresses[i][j] != null){
if (expresses[i][j].getCode() == code){
return expresses[i][j];
}
}
}
}
return null;
}
/**
* 根据快递单号寻找快递
* @param number 快递单号
* @return 快递
*/
public Express findExpressByNumber(String number){
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
// 空指针异常,,
if(expresses[i][j] != null){
// number 是引用类型,需要用equals
if (expresses[i][j].getNumber().equals(number)){
return expresses[i][j];
}
}
}
}
return null;
}
@Override
public String toString() {
return "ExpressData{" +
"expresses=" + Arrays.toString(expresses) +
", count=" + count +
'}';
}
}
UserClient.java
public class UserClient {
// 视图
View v;
// 快递柜
ExpressData expressData;
public UserClient(View v, ExpressData expressData) {
this.v = v;
this.expressData = expressData;
}
/**
* 取件
*/
public void getExpress(){
// 输入取件码
int code = v.userMenu();
// 根据取件码取件
Express express = expressData.findExpressByCode(code);
// 没有找到快递就认为取件码不正确,因为取件码只有放进柜子才会有取件码
if (express == null){
v.getFailure();
}else{
v.showExpressView(express);
v.success();
// 取件之后不要忘记从快递柜子删除
expressData.delete(express);
}
}
}
View.java
import java.util.Scanner;
/**
* 视图类,主要负责显示
*/
public class View {
Scanner input = new Scanner(System.in);
/**
* 输出欢迎界面
*/
public void welcome() {
System.out.println("欢迎使用战翼快递柜系统!(点击屏幕进入操作菜单)");
}
/**
* 拜拜界面
*/
public void bye() {
System.out.println("欢迎下次使用。");
}
/**
* 初始操作选择界面
* @return 操作指令
*/
public int identityView() {
System.out.println("------------------------------------------------------------------------");
System.out.println("请选择操作类型(输入 0/1/2):");
System.out.println("1:管理快递");
System.out.println("2:取件");
System.out.println("0:退出");
int command;
try {
command = Integer.parseInt(input.nextLine());
} catch (RuntimeException e) {
System.out.println("您输入的命令格式不正确,请重新选择。");
return identityView();
}
if (command < 0 || command > 2) {
System.out.println("您输入的命令不存在,请重新选择。");
return identityView();
}
return command;
}
/**
* 身份验证界面
* @return 验证信息
*/
public String[] authenticationView(){
System.out.println("------------------------------------------------------------------------");
System.out.println("您选择了管理快递,请进行身份验证:");
System.out.println("请输入您的管理员帐号:");
String account = input.nextLine();
System.out.println("请输入您的管理员密码:");
String password = input.nextLine();
String[] messages = new String[2];
messages[0] = account;
messages[1] = password;
return messages;
}
/**
* 管理员身份验证成功
*/
public void authenticationSuccess(){
System.out.println("身份验证成功,欢迎你,亲爱的管理员!");
}
/**
* 管理员身份验证失败
*/
public void authenticationFail(){
System.out.println("身份验证失败,请输入正确的账号密码!");
}
/**
* 管理员操作菜单
*
* @return 操作指令
*/
public int administratorMenu() {
System.out.println("------------------------------------------------------------------------");
System.out.println("欢迎使用管理员系统,请选择您要执行的操作(输入相应操作的数字:0/1/2/3/4):");
System.out.println("1: 快递信息录入");
System.out.println("2: 删除快递");
System.out.println("3: 快递信息修改");
System.out.println("4: 查看当前所有快递");
System.out.println("0: 退出");
int command;
try {
command = Integer.parseInt(input.nextLine());
} catch (RuntimeException e) {
System.out.println("您输入的命令格式不正确,请重新选择。");
return administratorMenu();
}
if (command < 0 || command > 4) {
System.out.println("您输入的命令不存在,请重新选择。");
return administratorMenu();
}
return command;
}
/**
* 用户操作菜单
*
* @return 取件码
*/
public int userMenu() {
System.out.println("------------------------------------------------------------------------");
System.out.println("请输入您的取件码进行取件:");
int code;
try {
code = Integer.parseInt(input.nextLine());
} catch (RuntimeException e) {
System.out.println("格式有误,请重新输入。");
return userMenu();
}
if (code < 100000 || code > 999999) {
System.out.println("取件码格式错误,请重新输入。");
return userMenu();
}
return code;
}
/**
* 快递信息录入
*
* @return 快递
*/
public Express addView() {
System.out.println("------------------------------------------------------------------------");
System.out.println("请输入快递单号:");
String number = input.nextLine();
System.out.println("请输入快递公司:");
String company = input.nextLine();
Express express = new Express(number, company);
return express;
}
/**
* 获取用户输入的单号
*
* @return 单号
*/
public String getExpressNumberView() {
System.out.println("------------------------------------------------------------------------");
System.out.println("请输入要操作的快递单号:");
return input.nextLine();
}
/**
* 显示快递的详细信息
*
* @param express:快递
*/
public void showExpressView(Express express) {
// if (express == null) {
// System.out.println("不存在此快递。");
System.out.println("快递详细信息:");
System.out.println("------------------------------------------------------------------------");
System.out.println("快递在第" + express.getRow() + "行" + ", 第" + express.getColumn() + "列:");
System.out.println("快递单号:" + express.getNumber());
System.out.println("快递公司:" + express.getCompany());
System.out.println("取件码:" + express.getCode());
System.out.println();
}
/**
* 根据单号删除快递
*
* @return 是否进行删除
*/
public String deleteView() {
System.out.println("------------------------------------------------------------------------");
System.out.println("是否进行删除(y/n)");
String command = input.nextLine();
if (command.equals("y") || command.equals("n")) {
return command;
} else {
System.out.println("命令不存在!, 请重新输入。");
return deleteView();
}
}
/***
* 对快递信息进行修改
* @param express:快递
*/
public void updateView(Express express) {
System.out.println("------------------------------------------------------------------------");
System.out.println("请输入新的快递单号:");
String number = input.nextLine();
System.out.println("请输入新的快递公司:");
String company = input.nextLine();
express.setNumber(number);
express.setCompany(company);
}
/**
* 显示所有快递信息
*
* @param expresses:快递柜
*/
public void showAllExpressView(Express[][] expresses) {
int count = 0;
if (expresses == null) {
System.out.println("当前没有快递。");
} else {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (expresses[i][j] == null) continue;
count++;
System.out.print("第" + count + "个");
showExpressView(expresses[i][j]);
}
}
}
}
/**
* 操作成功
*/
public void success() {
System.out.println("操作成功!");
}
/**
* 添加快递失败
*/
public void addFailure() {
System.out.println("操作失败!快递柜已满,请等待取件后重新操作。");
}
/**
* 取件失败
*/
public void getFailure() {
System.out.println("取件码错误!");
}
/**
* 没有查找到快递
*/
public void getNull(){
System.out.println("查无此快递。");
}
/**
* 快递已经存在
*/
public void exist(){
System.out.println("此快递已经存在。");
}
}
AdministratorClient.java
import java.util.Arrays;
public class AdministratorClient {
// 视图
View v;
// 快递柜
ExpressData expressData;
// 管理员账号数组 假设只有三个管理员,账密位置一一对应
private final String[] accountData = {"admin1", "admin2", "admin3"};
// 管理员密码数组 假设只有三个管理员
private final String[] passwordData = {"123", "456", "789"};
public AdministratorClient(View v, ExpressData expressData) {
this.v = v;
this.expressData = expressData;
}
/**
* 身份验证
* @param account :账号
* @param password : 密码
* @return 验证是否成功
*/
public boolean authentication(String account, String password){
for (int i = 0; i < 3; i++) {
if(accountData[i].equals(account)){
if(passwordData[i].equals(password)) return true;
}
}
return false;
}
/**
* 管理员管理操作
*/
public void management() {
while (true) {
// 获取操作命令
int command = v.administratorMenu();
switch (command) {
case 1: {
// 添加快递 需要考虑这个快递是否已经在快递柜
Express express = v.addView();
// 已经存在
if(expressData.findExpressByNumber(express.getNumber()) != null){
v.exist();
break;
}
// 不存在 需要考虑能否添加成功(即快递柜是否还有位置)
if (expressData.add(express)) {
v.success();
} else {
v.addFailure();
}
break;
}
case 2: {
// 删除快递
// 得到需要操作的快递单号
String expressNumber = v.getExpressNumberView();
// 根据单号查找快递
Express express = expressData.findExpressByNumber(expressNumber);
// 不存在此快递
if (express == null){
v.getNull();
}else{
// 打印快递信息
v.showExpressView(express);
String deleteCommand = v.deleteView();
if (deleteCommand.equals("y")) {
if (expressData.delete(express)) v.success();
}
}
break;
}
case 3: {
// 快递信息修改
// 输入要修改的快递单号
String expressNumber = v.getExpressNumberView();
// 按照快递单号找到快递
Express express = expressData.findExpressByNumber(expressNumber);
// 不存在此快递
if (express == null){
v.getNull();
}else{
// 显示快递信息
v.showExpressView(express);
// 备份旧快递,多余的操作,因为在v.updateView中已经把快递信息修改了,不过,没有取件码
Express newExpress = express;
// 更新快递
v.updateView(newExpress);
// 更新数据库中的快递,这样更新之后才有取件码
expressData.update(express, newExpress);
v.showExpressView(express);
v.success();
}
break;
}
case 4: {
// 查看当前所有快递
v.showAllExpressView(expressData.findAll());
break;
}
// 退出
case 0:
return;
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AdministratorClient that = (AdministratorClient) o;
return Arrays.equals(accountData, that.accountData) ||
Arrays.equals(passwordData, that.passwordData);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(accountData);
result = 31 * result + Arrays.hashCode(passwordData);
return result;
}
}
Express.java
import java.util.Objects;
/**
* 快递类,属性包括 快递单号,快递公司,取件码,快递位置
*/
public class Express {
// 单号
private String number;
// 快递公司
private String company;
// 取件码
private int code;
// 位置 初始值为 -1 ,因为默认初始值为0,0 ,但是每个快递刚开始是没有位置的
private int row = -1, column = -1;
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getColumn() {
return column;
}
public void setColumn(int column) {
this.column = column;
}
@Override
public String toString() {
return "Express{" +
"number='" + number + '\'' +
", company='" + company + '\'' +
", code=" + code +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Express express = (Express) o;
return number.equals(express.number);
}
@Override
public int hashCode() {
return Objects.hash(number, company, code);
}
public Express() {}
public Express(String number, String company) {
this.number = number;
this.company = company;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
在这个案例中,我学到了分块编写,特别的就是把视图分出去,在写的时候,总想在其他地方写一些打印语句,就是因为不习惯。然后就是发现自己还是不能自然的就面向对象,总感觉哪里有点不对,总想着封装方法,还是对于面向对象编程不熟悉,这一次也是切实的感受到了好处,比如,返回值这一块,我之前封装方法时,遇到多返回值,我就懵了,特别是不同类型的返回值,那对象就很方便了,直接把这个对象的属性设置成要返回的值,然后把对象返回,就很方便的可以获得各种返回值。还有一个问题,就是空指针问题,这个一定要进行处理,我总是再根据报错信息自己捋,但是我遇到的一个空指针就是因为,我仅仅是初始化了一个对象,但是没有给它赋值,然后引起的空指针,所以我怎么看我的报错哪行,都不理解,最后是通过调试,找到了这个问题。一定一定要善用调试,自己从逻辑上找不到错误的时候,用这个真的很方便。这一个案例就写了几百行的代码,分了好几个类,对于逻辑的训练和面向对象思维的训练就很大。