Java学习-04 面向对象
这阵子主要学习了Java的面向对象。面向对象是相对于面向过程来讲的,从面向过程到面向对象,是程序员思想上从执行者到指挥者的转变。面向对象强调的是直接以现实中的事物,也就是对象,为中心来思考,认识问题,并根据它们各自的本质特点(共性),把它们抽象为java中的类,这样的方式与现实世界有着更好的映射,而且也更符合人类的思维习惯。
想学好面向对象,光靠看听是远远不够的,一个合格的程序员同时也是一个辛勤的耕耘者,一定要多写代码,只有在练习的过程中,才会去想这个地方怎么做,用什么做,怎么优化,遇到问题是不可避免的,但也是问题让我们对于每一个问题背后的知识点掌握的更加深刻。接下来就结合具体的例子说一说我学习中遇到的知识点。
1、匿名对象
我们现在有一个 Person类,它有名字和年龄两个属性,还有一个可以输出姓名年龄的say方法。下边这个代码将会输出什么?
new Person().name = "张三";
new Person().age = 18;
new Person().say();
// 这三个是毫无关系的三个对象
它不会输出 张三 和 18,而是输出Person类的默认值。因为,每一次new都相当于在堆内存中新建一个新的对象,而这个对象如果没有指向它的引用,它最多可以使用一次,这就是匿名对象了,可以类比匿名信,只能和写信的人存在这一次交流,然后你就找不到他了。所以,前两行的代码,在程序执行到第三行的时候就已经找不到了,所以say输出的就是默认值。
2、静态
静态修饰的方法,被调用时,有可能对象还未创建。
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访
问。并且不会因为对象的多次创建, 而在内存中建立多份数据。
相关使用:static 可以用来修饰 这个类都具有同一个值的属性,这样方便直接修改。
在访问时: 静态不能访问非静态 , 非静态可以访问静态 !因为静态加载完毕的时候,非静态可能还没有加载到内存中。
静态代码块只会在类加载时执行一次,而构造代码块在每次创建对象时都会执行,且在构造方法之前。
面试题:
构造方法 与 构造代码块 以及 静态代码块的执行顺序:
静态代码块 --> 构造代码块 --> 构造方法
3、main方法
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 接收命令参数,什么也不输入,就什么也不输出,输入数字什么的,要用空格隔开,接收进来的就是一个字符串数组。
对于本来就有空格的字符串,要用" "括起来,才能输出你想要的:
否则,输出的就是按空格分割的单词:
4、继承
java只有单继承。
内存图:
super 保存父类地址,所以可以用它调用父类的东西,因为它就相当于父类的引用。
5、构造器重载
回顾了构造器重载,以及使用this来减少代码的重复,使用this调用另一个重载的构造器只能在构造器中使用,而
且必须作为构造器执行体的第一条语句。
为什么要用this来调用另一个重载的构造器?
如图,这种情况,我们就可以使用this调用构造器A,而不用在B,C,D中都写上重复的代码了。使用this的好处
是,如果有一天我们需要对构造器A中的代码进行修改时,如果B,C,D甚至更多的构造器中不是用的this,而是
和A一样的代码,也需要修改,那么工程量就很大了,用this的话,就只需要修改A中代码即可。这样可以充分的利
用每一段代码,既可以让程序简洁,也降低了维护成本。
6、重写以及与重载的区别
面试题:
Java中重写(Override)与重载(Overload)的区别
1、发生的位置
重载:一个类中
重写:子父类中
2.参数列表限制
重载:必须不同的
重写:必须相同的
3、返回值类型
重载:与返回值类型无关
重写:返回值类型必须一致
4、访问权限:
重载:与访问权限无关
重写:子类的方法权限必须不能小于父类的方法权限
5、异常处理:
重载:于异常无关
重写:异常范围可以更小,但是不能抛出新的异常。
7、抽象类
抽象类就像一个大纲。抽象,顾名思义,就是不够具体,也就是说,只有一个大概。那么抽象类和抽象方法,也就是这么一个意思,我们知道大概要有这么个东西,但是呢,没办法在当时就立马完全的把所有细节搞定,因此先写个抽象类,抽象方法,类似于论文的大纲,大概那么个东西,后期可能还会修改,把它具体化。
在抽象类的使用中有几个原则:
- 抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。
- 一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须重写抽象类中的全部抽象方法。
一些容易出错的概念:
1、 抽象类能否使用final声明?
不能,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。
2、 抽象类能否有构造方法?
能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默
认是无参的),之后再调用子类自己的构造方法。
8、异常处理
在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);
9、递归
递归的一般要求:
- 要有出口,不然会死循环。
- 一个大问题的解可以由它的小问题的解给出,并能不断地缩小问题规模。
递归的应用很广泛,解决问题的思路也很符合我们人类的思维习惯,就是不断的简化问题,直到我们能够直接解决。对于树结构来说,递归遍历是经常性的操作。
递归可以解决的一些问题:斐波那契数列,汉诺塔问题等等。
一般来说,采用递归的时候,最好画出递归树,这样便于观察是否有大量的冗余计算。
递归的格式决定了它会占用大量的栈内存,因此能不用就不用。
10、面向对象案例练习
案例要求:
快递管理案例:
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]