面向对象编程OOP——Java的核心思想
1.初始面向对象
(1)面向过程&面向对象
- 面向过程:线性思维(微观操作、流程)
- 面向对象:分类的思维方式(宏观、框架)
- 属性+方法=一个类
(2)面向对象OOP
- 抽象:抽象出共同部分,剥离出来
- 特征:封装、继承、多态
- 封装:封装代码数据,对外提供一个接口来访问
- 继承:子类继承父类的所有东西
- 多态:同一个方法表现出不同形态
2.方法回顾和加深
(1)方法的定义
- 修饰符
- 返回类型
- break 和 return 的区别(强调)
break: 跳出 switch,结束循环
return:结束方法,返回一个结果。这个结果可能为空 - 方法名:注意规范就ok,并且要见名知意。驼峰命名规范,开头小写,后面每个单词开头大写
- 参数列表:(参数类型 参数名)…
- 异常抛出:后面讲解
(2)方法的调用
- 静态方法:static
调用时:类名.方法名
- 非静态方法
先通过new关键字实例化这个类
然后创建对象:对象类型 对象名 = 对象值
最后调用:对象名.方法名
a和b都是普通方法或者都是静态方法,他们之间是可以互相调用的
如果a是静态方法,而b是普通方法,则a不能调用b
原因:static 的a方法是和类一起加载的,有了类就有方法,时间片特别长;但是b方法跟对象有关,类实例化为对象之后才存在。存在的东西调用还不存在的东西,所以会报错
- 形参和实参:实际参数和形式参数的类型要对应
- 值传递和引用传递(注意例子对比)
Java中都是值传递
引用传递:一个对象
- this关键字:继承和多态的时候再说
3.对象的创建分析
(1)类与对象的关系
- 类是一个模板,是抽象的。
- 对象是一个实例,是具体的。
(2)创建与初始化对象
1)创建对象
- 一个项目应该只存在一个main方法
- 一个类只有属性和方法
- 类是抽象的,要进行实例化。类实例化后会返回一个自己的对象!
- 同一个类可以有不同的对象,但有共同的属性
2)构造器
- 一个类即使什么都不写,也会存在一个方法(构造方法,构造器)
- 不写的话则会默认生成
- 也可以显式的定义构造器:与类名相同,且没有返回值的无参构造
- 用new关键字实例化了一个对象Person person = new Person();时,其实已经包含了public Person(){ }
构造器能干吗:
有参构造
- 一旦定义了有参构造,要想通过无参构造去创建对象的话Person person = new Person();,无参构造就必须显式定义,否则无效。
- 总结:所以在正常情况下,如果定义了有参构造,就默认把无参构造写在上面,留个空就行
下面的 public Person(String name) 是重载,上面的是 public Person() 是默认的构造器 - 快捷键:Alt + insert 后弹出Generate,选择第一个construct ,弹出一个窗口,有个name,点OK,就会自动生成
public Person(String name){
this.name = name;
}
若想要无参,Alt + insert 后弹出Generate,选择第一个construct ,弹出一个窗口,选择SelectNone,就会生成
public Person(){
}
若定义了多个属性,也可以快捷生成多个属性对应的有参构造
- 总结:
- 使用new关键字,本质是在调用构造器
- 用来初始化值
- 注意点:定义了有参构造之后,如果想要使用无参构造,必须显式定义一个无参的构造
- this:代表的是当前类 this.XX表示的是当前类的属性 this.XX = 等号后面的那个一般是参数传进来的
- 注意:如何在IDEA中打开out目录
- 目录结构和代码文件一样,里面都是编译好的 class 文件
*创建对象内存分析
实际内存图会复杂很多,还有类加载,class对象什么的。这里的内存分析专门为了理解
举例的代码如下:
- 首先有一个Application类。里面有一个main方法,还有字符串常量值(旺财),年龄只是一个数字,不是常量值。
- 然后程序开始main()方法, 把main压入栈底(一般来说,最后main方法执行完之后从栈中出来,程序就结束)
- debug进行下一步,进行到Pet dog = new Pet(); ,要去加载Pet这个类。
- Pet类中有属性字段name、age,还有方法shout()。(还有常量值,这里没有)当前name为null,age为0
- 通过Pet这个类模板生成一个dog对象,栈中多了一个dog对象(只是个引用变量名)。真正的对象在堆中,new了一个Pet,name为null,age为0,shout()调用了下面Pet类的shout()。
- 再走下一步执行dog.name = “旺财”; dog.age = 3; ,把Application中的name和age赋值给dog对象
- 如果此时又new一个cat对象。则此时栈中又多了一个 cat 引用变量名。真正的对象在堆中,new了一个Pet,name为null,age为0,shout()调用了下面Pet类的shout()。然后接着重复上述的步骤。
- 引用变量真正指向的还是堆中
- 静态方法区。所有static的东西都在这里。一开始就和类一起加载的.所有的对象都可以直接调用它。
- 总结:左边是栈,右边是堆。堆中有一块特殊区域叫做方法区。方法区也属于堆。堆中一般放具体创建出的对象。而栈里面都是方法和变量的引用。
因此,同时new的都是Pet,但是产生不一样的对象。因为它产生的是不同区域,地址可能不同,引用变量也不同。
简单小结类与对象
1.类与对象
类是一个模板,抽象的。对象是一个具体的实例。
2. 方法(定义和调用!)
3.对象的引用
引用类型:除了八大基本类型之外的都可以叫做引用类型
对象是通过引用来操作的。就是内存图中在栈中的,真实对象是在堆中的
4.对象的属性
- 别名:字段 field、成员变量
- 定义了属性,会默认初始化:
数字 0/0.0 ;
char u0000 ;
boolean false ;
引用类型 null - 属性的定义: 修饰符 属性类型 属性名 = 属性值
5.对象的创建和使用
- 必须使用new关键字创建对象,必须有构造器 Person zhangpipi = new Person();
- 对象的属性 zhangpipi.name
- 对象的方法 zhangpipi.sleep()
6.类(类中只有属性和方法)
- 静态的属性 属性
- 动态的行为 方法
4.面向对象三大特性(重点)
(1)封装 → 属性私有 privite, get/set
- 封装大多数时候是对于属性来的;对方法来说用的比较少。重点是对于属性。这个地方写方法是为了大家理解其实方法是用不了多少封装的。
- 属性私有:private。 和public相对
- 提供一些可以操作这个属性的方法→提供一些 public 的 get、set方法
get:获得这个数据(属性) set:给这个数据设置值
没有直接去操作属性,而是通过它提供的一些外部的方法对它进行操作。通过set进行设值,通过get方法进行获取。
- 快捷键 Alt + insert(相当于右击后的Generate): 自动生成 get 和 set 方法.直接选择get and set方法
选中你需要对哪个属性生成 get 和 set 方法
- 快捷键 输入s1.getAge().sout →就会变成 System.out.println(s1.getAge());
即输入 输出内容.sout →就会变成 System.out.println(输出对象);
封装的意义
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节。调用者不知道类里面干了什么
- 统一接口。所有的方法都是 get 和 set 。形成了规范,用户调用的接口就是这个
- 提高了系统的可维护性
* 插播一句,方法的重载
eg :println。点进去看源码 。方法重载,同名不同参数
- 方法名和参数列表要是相同,这个方法一定相同。看方法名和参数列表!!!!!
(2)继承
- 一个儿子只能有一个爸爸,一个爸爸可以有多个儿子。
- 组合关系实例(以后再讲)
- 子类可以继承父类的所有方法。子类继承了父类,就会拥有父类的全部方法!
- 私有的东西是无法继承的。
- 四个优先级修饰符: public 公有的 protected 受保护的 default 默认的 private 私有的
其中,优先级最高的是 public 。子类继承父类的东西一般都是 public 方法,get 、set 也是。一般属性是私有的。 - 按照封装的思想,父类的属性也应该设置成私有的,然后给他设置两个公共的方法。
- 快捷键 Ctrl + H :侧边打开继承树,表示继承关系
在哪个地方点,就出现对应的所有继承关系
object 类
- 在Java中,所有的类,都默认直接或者间接继承Object
即使 Person 中什么都没有,也会有很多方法
可以进入Object中看,String也是继承的Object。在Java中,所有的类,都默认直接或者间接继承Object
显式定义没啥用,正常情况下不写。总之,你写一个类,即使不继承任何东西,他其实也是继承 Object 类的。
super(可以对比 this 来学习。一个是父亲,一个是当前的)
- 分不清谁是谁的 name 或者 print 的话,可以 Ctrl + 左击跳转查看。
- 属性
- 方法
- 私有的东西无法被继承
- 调用构造器
默认调用了父类的构造器 super()
调用父类的构造器,必须在子类构造器的第一行
正常情况下不写这段代码,默认是调用父类的无参构造
如果要调用子类的构造器,也必须在第一行。
所以,调用构造器时,要么调用父类的构造器,要么调用子类的构造器。因为都必须在代码的第一行,必须保证在构造器的第一个。
如果父类没有无参构造,那么子类既不能调用父类的无参构造,自己也不能写无参构造。
所以必须要有一个无参构造。一个类无论怎么写,只要重写了有参构造,一般就是先把无参构造加上。
如果非要用有参,那子类就得显式写出调用父类的有参构造。不然不写的话就是默认调用父类的无参构造。
super总结笔记
- super 注意点:
- super 调用父类的构造方法,必须在构造方法的第一个
- super 必须只能出现在子类的方法或者构造方法中!(不然在自己的类中调用 super,不然调用到 object 中去了)
- super 和 this 不能同时调用构造方法。(因为同时调用的话,它必须保证在第一行,他们肯定有一个报错)
- 对比 this
- 代表的对象不同
this :本身调用者这个对象(谁调用就是谁)
super :代表父类对象的引用 - 使用的前提不同
this :没有继承也可以使用
super :只能在继承条件下才可以使用 - 构造方法不同
this() :本类的构造
super() :父类的构造
方法重写(重点,关系到多态)→ 只和非静态方法有关
方法重写只和非静态方法有关,因为静态方法在类加载的时候就已经出来了。
- 重写都是方法的重写,和属性无关
- 静态方法:方法的调用只和左边,集定义的数据类型 有关
- 非静态方法(重写只和非静态方法有关)
删掉 static 之后(非静态方法),明显多了两个圈。这个O 就代表重载
把子类中的方法删掉,Alt + insert ,里面有一个 Override Method 重写方法
它默认调用的是父类的 test() 方法,super.test(); 。但是我们也可以重写自己的方法。
正常情况下,看IDEA有没有出现箭头(那个 O),出现了就是重写了 - 重写的关键词必须是 public
方法重写总结:
子类继承父类,当父类的方法不适用于子类时候,进行方法重写。
- 前提:需要有继承关系。而且是子类重写父类的方法!!!(方法,属性不行!)
- 方法名必须相同(刚刚Override 它会自动生成)
- 参数列表也必须相同(否则就是重载了,而且重载是当前方法)
- 修饰符:范围可以扩大,但是不能缩小 public > protected > default >private(子类重写的方法的访问范围要大于等于父类方法的访问范围。但父类的私有方法 private 不能被重写)
- 抛出的异常:可以被缩小,但不能扩大。(重写异常抛出范围不能大于父类)
eg:ClassNotFoundException ------> Exception(大)
- 重写:子类的方法和父类必须要一致,方法体不同。
- 为什么需要重写:父类的功能:子类不一定需要,或者不一定满足!
- 重写的快捷键: Alt + insert ,选择Override
(3)多态
-
关键词 instanceof :类型转换。指的是引用类型之间的转换
-
一个类的实际对象它的类型是确定的,但是指向这个对象的引用类型却可以是任意的(它的父类型或有关系的类),因为父类的引用可以指向子类的类型
-
虽然 new 的是 student ,但是依旧走了父类的方法。因为子类继承了父类的全部方法
-
在子类中重写父类的方法,执行的是子类的方法。因为现在调用的方法是 student,虽然它的类型是 Person。
-
在子类中新增 eat()方法,父类中无该方法,则父类无法调用子类的方法
-
如果 Person 和 Student 中都有该方法的情况下,只要子类没有重写父类的方法,则调用的是父类的方法。
-
如果 Person 和 Student 中都有该方法的情况下,只要子类重写了父类的方法,则调用的是子类的方法。
-
总结:
-
即对象能执行哪些方法,主要看对象左边的类型,和右边的关系不大
-
Student (子类)能调用的方法都是自己的或者继承父类的!
-
Person (父类型),可以指向子类,但是不能调用子类独有的方法!
-
通过类型转换可以调用 eat() 方法。Person 转成 Student ,高转低。(后面还会讲)
多态注意事项:
- 多态是方法的多态,属性没有多态
- 父类和子类必须有联系,存在父子关系才可以进行转换。否则会类型转换异常!ClassCastException!
- 存在条件:继承关系,方法需要重写,父类的引用指向子类对象,形如 Father f1 = new Son()
有些方法不能被重写:① static 静态方法,属于类,它不属于实例
② final 常量的,无法重写,无法改变(它是在常量池里的)
③ private 私有的方法
- 关键词 instanceof :类型转换。指的是引用类型之间的转换。判断一个对象是什么类型~
instanceof 和类型转换
- 上面三个满足父子关系,而且是这个类型,所以是 true
- 所以可以用 instanceof 判断这两个类型是否相似,然后再进行强制转换
- System.out.println(X instanceof Y); 能不能编译通过!就看 X 和 Y 之间是否存在父子关系
看 X 所指向的实际类型是否为 Y 的子类型,是否有关系
- 类型之间的转化:
- 高转低要用强制转换
- 低转高会自动转过去,但是子类转换为父类,可能丢失自己本来的一些方法!
总结:
- 父类的引用指向子类的对象
- 把子类转换为父类,向上转型,不用强制转换,可以直接转化过去
- 把父类转换为子类,向下转型,需要强制转换。像之前强制类型转换会丢失精度一样,子类转换为父类,可能丢失自己本来的一些方法!
- 方便方法的调用,不用重新 new 一个类,直接降级或者升级就可以调用它的一些其他方法,减少重复的代码!
static 关键字详解
-
静态属性
-
静态变量对于类而言,在内存中只有一个,它能被类中的所有实例去共享。
如果想要很多类去操作他的话,我们就会用static。以后会在多线程中讲到!
静态变量建议通过类名直接访问
-
静态方法
非静态方法可以直接去访问这个类中的静态方法,静态方法可以去访问静态方法。
因为静态的东西和类一起加载,而非静态的还没加载,所以不能直接调用,必须要new一个对象。
main方法也是一个静态方法。
-
静态代码块
在Java中,所有的代码都在代码块中,无论是方法还是类
有时候为了初始化一些东西,我们会用静态代码块,用于加载一些初始化数据 -
匿名代码块:程序在执行的时候并不能主动调用这些模块。匿名代码块在创建对象时自动就创建了,而且它在构造器之前。因为匿名代码块是和对象一起产生的,所以可以用于赋初始值。
-
静态代码块:和类一加载时它就直接执行,永久执行一次
因此,对象一创建,就会先走匿名代码块,然后走构造方法。如果有静态代码块,则在最开始执行,且只执行一次。
因为匿名代码块是和对象一起产生的,所以可以用于赋初始值。 -
静态导入包(一般不这样,只是告诉你java里面东西有很多)
如果不想写Math这个类
final之后断子绝孙
- 通过final修饰的类不能被继承,没有子类
5.抽象类和接口
(1)抽象类(了解)
- abstract 抽象
- 抽象方法:只有方法名字,没有方法的实现。可用于约束~有人帮我们实现(只会写大型框架,就直接写约束,其余别人写)
- 抽象类的所有方法,继承了它的子类,都必须要实现它的方法!除非它的子类也是abstract抽象的,则它的子类无需实现,它的子子类就必须要实现。
- 抽象类的本质是一个类,类需要继承,逃脱不了extends这个关键字。而extends是单继承的,无法继承多个类具有局限性。
- 想办法使之能够继承多个类,即多继承。Java中无多继承。
- 但接口可以多继承!!!!即类是单继承,接口可以实现多继承!(相当于一个插座,是一个约束,约束了可以插什么头,但没有约束什么电器才能插上去)
抽象类的特点:抽象的抽象,即一种约束!
- 不能new这个抽象类,只能靠子类去实现它!就是一个约束。所以只能new它的子类对象,如果子类对象没实现它,那就子子类。
- 抽象类中可以写普通的方法。
- 抽象方法必须在抽象类中。(若方法中存在了抽象方法,这个类一定是抽象类)
思考题: - 抽象类存在构造器。抽象类可以被继承,继承最起码有空参构造器。子类继承父类后,需要通过父类的构造器来实例化,所以抽象类也需要构造器。
- 抽象类存在的意义:可以提升开发效率,因为抽象类里面可以写一些公共的方法,这些确实可以达到复用
(2)接口(重点)
- 接口:比抽象类还要抽象,专业的抽象和约束,实现约束和实现分离。大型公司都是面向接口编程~架构师写好接口,我们就实现接口写里面的方法即可。
- 接口中的所有定义的方法其实都是抽象的 即 public abstract。即,你不写,默认就是 public abstract
- 所以以后接口,正常就可以这样写,直接就是:返回值类型 方法名(参数) 。参数根据需要写,也可以无参。
- 接口也可以定义一些常量和变量,public static final 。一般没什么人会在接口中定义常量
- 接口定义的关键字是 interface ,接口都需要有实现类。一般实现类我们都会用 impl 结尾
- 关键词 实现:implements
- 类可以实现接口,通过 implements关键字实现接口
- 实现了接口的类,就必须重写接口中的方法
- 利用接口实现了多继承~
- 与继承的类不同的是,类中会有方法的实现,而接口中只有方法的定义,通过interface来定义
接口的作用:
- java的接口是一个约束
- 定义一些方法,让不同的人去实现。用的一个接口,但是是不同的实现
- 接口中的方法都是 public abstract
- 接口中的常量都是 public static final
- 接口不能被实例化,(抽象类都不能被实例化),接口中没有构造方法
- 通过 implements 关键字可以实现多个接口
- 实现接口必须要重写接口中的方法
6.内部类及OOP实战
(1)内部类(奇葩)
- 成员内部类
- 通过这个外部类来实例化内部类~
- 内部类访问外部类的私有变量(私有属性和私有方法)
- 静态内部类(先认识一下,以后再讲)
一旦加了static,这个 id 就拿不到了。因为先实例化了 Inner,那个时候 private id 还没有。除非把上面也改成 static 才可以。
- 一个java文件中只能有一个public class,但是可以有多个class文件。
- 有时可以在这里写测试类,因为这里面可以写方法
- 类还可以写在方法里
- 匿名内部类
6.异常机制
(1)什么是异常
- 例如:循环依赖
- 例如:被除数不能为0
- 简单分类
(2)异常体系结构
- 错误
- 异常
(3)Java异常处理机制 &&(4)处理异常
- finally 可以不要,一般 IO、资源等需要关闭的东西,可以用finally
- try 和 catch 一般两个都有
- 未捕获到,但是finally仍然会输出,处理善后工作
- catch 里的参数就是想要捕获的异常类型
- 也可以捕获多个异常
- 有个层层递进的关系,eg :Throwable 要写到最后面,不能写在最上面。不然的话后面就不会捕获了
因此,假设要捕获多个异常,需要从小到大! - 生成快捷键:选中代码语句,Ctrl + Alt + t ,会出现一些代码块自动帮你包裹起来,第7个就是 try/catch,第8个是 try/catch/finally,一般我们会选择 try/catch/finally
- 老师的和我不一样,自动生成打印错误的栈信息。也可以用自己的逻辑判断。例如如果出现异常,程序就结束,自己手动结束程序。
- 主动抛出异常
- 输出没有什么变化,所以主动抛出异常一般在方法中使用,方法中 throw
- 方法上抛出异常(抛到更高阶)方法上 throws
如果不用 try/catch 的话,遇到错误程序就自动停止了。如果用 try/catch 捕获的话,程序还可以正常往下运行。即,如果错误在意料之中,可以让程序不停止。想办法让错误在 catch 中处理掉。
(5)自定义异常(用的不多)
总结
- 快捷键:出现红色波浪线,按住 Alt + Enter,自动会提示怎么做