面向过程:
- 线性思维 第一步做什么,第二步做什么…
- 处理简单问题
面向对象:
- 分类思维 物以类聚 解决问题的时候首先进行分类,然后对分类进行单独思考。
- 适合处理复杂问题,可用于多人协作
对于描述复杂的事物,从宏观上把握、从整体上分析,需要使用面向对象的思路来分析整个系统;而具体到微观操作,用面向过程的思路处理。
什么是面向对象面向对象编程(Object-Oriented Programming,OOP)
本质:以类的方式组织代码,以对象的形式封装数据
抽象
三大特性:
- 封装 把数据包起来
- 继承
- 多态
对象+…+对象→类 对象是具体的事物;类是对对象的抽象
从代码运行角度,先有类再有对象
回顾方法及加深 方法的定义- 静态的属性
- 动态的行为
package oop;
public class OopDemo01 { //class 表示一个类
//main 方法
public static void main(String[] args){
}
public String asyHello(){
return "hello,world";
}
public void hello(){
return;
}
public int max(int a,int b){
return a>b?a:b;
}
}
break:跳出switch,结束循环 return:结束本方法,返回一个结果
方法名:驼峰原则+见名知意
参数列表:(参数类型,参数名) …
异常抛出:
public void readFile(String file) throws IOException{
}
方法的调用
- 当方法是静态方法时,在另一个类中也可以通过 类名.方法名 进行调用
- 当方法是非静态时,需要将类实例化
package oop;
public class OopDemo02 {
public static void main(String[] args){
//实例化这个类 new
Student student = new Student();
student.say();
}
public static void a(){ //一个静态一个非静态的类不能相互调用
b();
}
public void b(){}
}
结果:
java: 无法从静态上下文中引用非静态 方法 b()
package oop;
public class OopDemo03 {
public static void main(String[] args){
int add = new OopDemo03().add(1,2); //当方法是静态的时候,直接引用即可 形式参数和实际参数的类型要对应
System.out.println(add);
}
public int add(int a,int b){ //形参
return a+b;
}
}
结果:
3
值传递:
package oop;
//值传递
public class OopDemo04 {
public static void main(String[] args){
int a = 1;
System.out.println(a);
change(a);
System.out.println(a);
}
public static void change(int a){ //没有返回值,a=10只存在这个方法中
a = 10;
}
}
引用传递:
package oop;
//引用传递:对象,本质还是值传递
public class OopDemo05 {
public static void main(String[] args){
Person person = new Person();
System.out.println(person.name);
change(person);
System.out.println(person.name);
}
public static void change(Person person){
person.name = "小龙"; //这里的Person是一个对象,指向Person这个类,修改的是Person类中的name
}
}
//一个类中只能有1个public class,但是可以有多个class
//定义了一个Person类,有一个属性:name
class Person{
String name;
}
结果:
null
小龙
类和对象的关系
- 类是一种抽象的数据类型/模板,对某一类事物的概括,但是不能代表这一类事务
- 对象是抽象概念的具体实例
使用new关键字创建对象:
- 分配内存空间
- 给创建好的对象进行默认的初始化
- 对类中构造器的调用
结果: null 0
结果: 小明 13 null 0
就体现了:以类的方式组织代码,以对象的形式封装数据
构造器也叫“构造方法”,是创建对象时必须要用的:
- 必须和类的名字相同
- 必须没有返回值类型,也不能写void
查看class文件
在Project Structure里点击Modules,添加一个根目录
就会生成这样的class文件
然后就可以看到,虽然源文件什么方法都没写,但是class文件会自动帮忙写一个方法(没有返回值),并且方法名==类名。
设置断点+Debug之后,就可理解构造器的作用:
构造器的作用- 使用new关键字,本质是调用构造器
- 用来初始化值
- 一旦定义了有参构造,无参就必须显示定义(无参构造就必须为空,如图)
但是如果加入了参数,就会调用有参构造,如下图所示:
快捷键Alt+Ins → Constructure → OK:默认生成有参构造
Alt+Ins → Constructure → select no:生成无参构造
创建对象的内存分析在Application中new了两个对象:dog和cat,同时还给dog的属性进行赋值,内存中的大概过程如下:
-
首先把Application这个类中的代码放入堆中的方法区,包括main()方法、常量池(3不是常量,是int类型的数字)
-
然后开始在栈中执行main()方法,执行了
Pet dog = new Pet();
这个操作,如图
new pet() 就需要将pet类加载到方法区,包括他自己的一些属性(name;age;shout)
通过这个模板生成了一个具体对象 dog (注意在栈中引用,实际上在堆中),在堆中实际分配了内存地址,同时也具有了刚刚引用到方法区中Pet的属性(属性也叫字段Field、成员变量)
-
然后执行对name、age的赋值
-
又new了一个pet:cat,和dog类似,但是没有对属性进行赋值
引用变量真正是指向堆中的具体对象,只是在栈中保存了一个变量名字。
注意:引入Application这些类的时候同时生成了"静态方法区",以便其他对象能够直接引用。
设计程序应该追求”高内聚,低耦合“
-
高内聚:类的内部数据操作细节自己完成,不让外部干涉
-
低耦合:仅暴露少量方法给外部使用
封装(数据的隐藏):禁止直接访问一个对象中数据的实际表示,应该通过操作接口进行访问
如下图所示,当属性是private时,没有name选项,当改成public时,就有了name。
举例:
Alt+Ins→Getter and Setter,点击OK就会自动帮忙生成get和set!
还可以在封装里添加检验条件,对输入进行检查
封装的作用:
- 提高程序安全性,保护数据
- 隐藏代码的实现细节(调用者看不到类干了什么)
- 统一接口(所有的接口都是get、set)
- 提高系统的可维护性
例如println()可以输出各种数据,就是因为里面写了很多方法的重载如上所示,查看源码之后发现里面的类很多,但是都叫println()。例如在括号里多加一个参数,就会变成另外一个类
继承- 对某一批类的抽象(是类和类之间的一种关系),以实现对现实世界更好的建模。
- 继承关系的两个类,一个是子类(派生类),一个是父类(基类)。子类继承父类用extends表示(is a)。
- 只有单继承(一个爸爸能有多个儿子,但是一个儿子只能有一个爸爸)。
继承:
组合:
public class Student{
Person person;
}
子类继承了父类,就会拥有父类全部的方法
如上图,子类Student继承了父类Person,所以Application中也有这2个方法
如果换成private或final(属性一般都是私有的),私有的是无法继承的
但是如果想要改变私有的变量,可以借助get、set。
继承树快捷键:ctrl+H
对Object类的解释:
删空Person类之后,在Application中仍然有很多方法。因为在Java中,所有的类都默认直接或间接阶乘Object类。如下图所示:
例1:
例2:
例3:
直接定义继承Object类,继承树仍然没有区别。省略
supersuper可以调用父类,如下图所示:
例1:(属性)
例2:(方法)
当然private属性和方法,super也无法调用
如下图所示,使用Alt+Ins在父类和子类分别创建一个无参构造器
如上图所示,Application调用子类Student,但是Student有隐藏代码,于是就先执行了父类,然后执行了子类。super(); 必须放在子类构造器的第一行,否则就会报错,如下图所示:
并且调用构造器的时候,因为super()和this都要放在第一行,所以要么调用父类要么调用子类。
写有参构造的时候,先写一个无参构造。假如在父类Person把无参变成有参,那么子类不仅会报错,还无法写无参构造。
但是如果写成super(“name”);(调用有参),是可以的
注意点- super调用父类的构造方法,必须在构造方法的第一行
- super只能出现在子类的方法或者构造方法中
- super和this不能同时用
super VS this:
- 代表的对象不同
- this:调用者本身这个对象
- super:代表父类对象
- 前提不同
- this:没有继承也可以使用
- super:只能在继承条件下才能使用
- 构造方法不同
- this:调用本类的构造
- 调用父类的构造
如上图所示,调用父类的方法,但是却指向了子类,是可行的。(理解成爸爸跟儿子要苹果吃)
去掉父类和子类方法中的static,然后删除子类方法,使用快捷键ALt+Ins,选择Override Methods,就剩成了方法重写/Override。该方法默认调用父类的test,如下图所示:
只更改A类的函数,其余不变,进行运行,发现结果出现差异:
这就是方法的重写。B b = new A(); 虽然new了一个A,指向的是B,实际上指向的是A——子类重写了父类的方法。
另外注意,出现了下图所示的箭头,才是重写
需要有继承关系,子类重写父类的方法
- 方法名相同,方法体不同
- 参数列表必须相同(否则就变成重载了)
- 修饰符的范围可以扩大,但是不能缩小(private→default→protected→public)
- 抛出异常的范围可以缩小,但是不能扩大(粗略理解为异常不能越来越多)
- ClassNotFoundException(小)不能变成Exception(大)
为什么要重写:
- 父类的方法,子类不一定需要,或者不一定满足
如图所示,子类继承父类的所有方法
用父类调用子类的方法时,如果父类没有该方法,就无法执行;如果子类和父类都有该方法,并且子类没有重写,那么就调用父类的;如果子类重写了父类方法,就调用子类的。 子类能调用的方法包括子类自己的方法和继承过来的父类的方法。
当然,也可以通过高类→低类,使他能成功运行:
((Student) s2).eat();总结:
- 同一方法可以根据发送对象的不同而采用多种不同的行为方式(子类重写了父类的run();方法,但是最后s1、s2的调用都调用到子类方法。)
- 一个对象的实际类型是确定的,但是指向对象的引用类型有很多(父类等有关系的类)
- 多态是方法的多态,属性没有多态
- 有父子继承关系 (要不会出现类型转换异常/ClassCastException!:Student→String)
- 存在的额条件:
- 继承关系
- 方法需要重写 (把子类方法里面的super.go(); 改写成System.out.println(“go”)
- 父类引用指向子类 (Father father = new Son()
- 不能重写的方法(不能重写也就更不能实现多态):
- static,属于类,不属于实例
- final, 常量,无法改变
- private
如上图所示:由于Object和Person跟Student是父子关系,所以前三个都是true,第四个和第五个是false。
将Object换成Person,除了最后一个由于完全没有联系之外,其余均有联系,结果不变。
换成Student之后,前三行都可能有联系,所以是true。最后两行属于两个分支,编译出现错误。
公式System.out.println(X is instanceof Y); //编译能不能通过就要看X和Y是否存在父子关系(可以是间接父子关系)
student是Person类,该类没有go方法,需要转化成其子类,如下图所示:
- 父类引用指向子类的对象
- 把子类转换为父类,向上转型即可,不用强制转换
- 把父类转换成子类,需要强制转换 (可能丢失一些方法)
- 方便方法的调用,减少重复的代码
- 静态属性
图中的静态变量age对于Studen这个类而言,在内存中只有一个;能被类中所有的实例共享。
- 静态方法
不能直接调用非静态的run方法,但是可以通过"对象.方法"的方式来调用:
new Student().run();
而静态方法的调用就比较简单了,如上图所示。
非静态方法run()可以调用静态方法go();静态方法只能调用静态方法。
静态代码块如上图所示,只在第一次调用才会率先执行静态代码块,然后依次执行匿名代码块和构造方法。
静态导入包可以通过导入包中的具体函数来减少后面的代码量。
抽象类继承了抽象类的子类需要进行重写或者同为抽象类,否则出错
抽象类不能new出来,如下图所示:
- 抽象类不能new出来,只能靠子类去实现他:约束!
- 抽象类里可以写普通方法,抽象方法必须在抽象类中
- 抽象的抽象:约束
- 抽象类存在构造器吗?
- 抽象类存在的意义是什么?
- 提高开发效率
- 提高可扩展性
普通(只有具体实现) < 抽象类(有具体实现和抽象方法,其中抽象方法由子类去实现) < 接口(只有规范,抽象类的抽象,只能实现约束,将约束和实现分离)
接口/interface就是规范,都要遵守、执行。接口里面写方法会报错
如果要实现接口里面所有的定义,必须要重写接口里面所有的方法
如下图所示,用接口实现多继承:
- 接口是约束
- 定义一些方法,让不同的人实现 (多个人用不同的方式实现一种方法)
- 接口的方法都是public abstract
- 接口的常量都是public static final
- 因为接口不是类,就没有构造方法,也就不能被实例化(new)
- 使用implements可以实现多个接口,但是需要重写里面的方法
动态内部类:
静态内部类:
加了static之后拿不到id了,因为率先执行静态代码块,然后才定义了id的值。除非把id的定义改成下面这样才能运行:
private static int id = 100;



