目录
3.2多态的弊端(会涉及向下转型和instanceof关键字)
一.什么是面向对象,什么又是面向过程?
对比面向过程,是有两种不同的处理问题的角度的
面向过程更注重处理事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象),及各自需要做什么?
比如:洗衣机洗衣服
面向过程:会将任务拆解成一系列的步骤(函数)/(方法),
1. 打开洗衣机-----> 2.洗衣服------> 3.放洗衣粉------> 4.清洗------> 5.烘干
面向对象:会拆出人和洗衣机两个对象:
人:打开洗衣机 放衣服 放洗衣粉(人可以称为类)(打开洗衣机 放衣服 放洗衣粉就是类中的方法)
洗衣机: 清洗 烘干(只需要把衣服为参数传给洗衣机即可)
从以上例子可以看出:面向过程比较高效,而面向对象更易于复用,扩展和维护
二.面向对象的三大特征
1.封装
1.1封装的好处
- ,在于明确标识出允许外部使用的所有成员方法,可以在不影响使用的情况下改变类的内部结构,同时保护了数据
- 内部细节对外部调用透明,外部调用无需修改或者关心内部实现
- 对于外界它的内部细节是隐藏的,暴露给外界的只能是它的访问方法
1.2属性的封装
使用者只能通过事先定制好的方法来访问数据,可以方便地加入逻辑控制,限制对属性的,不合理操作
javabean的属性私有,提供get,set方法对外访问(set赋值)(get获取),因为属性的赋值或者获取逻辑只能由javabean本身决定赋值规则,而不能由外随意修改
//创建一个女朋友
public class GirlFriend {
private int age;
public void setAge(int age) {
if (18 < age && age < 40) {
this.age = age;
}else {
System.out.println("我并没有这个岁数的女朋友,你认错了");
}
}
}比如上例:哥们你也不想你的女朋友是个60岁的吧,所以就要用封装,不让别人乱给你定女朋友年龄
1.3方法的封装
使用者按照既定的方式调用方法,不必关心方法内部的实现,便于使用;便于修改,增强代码的可维护性;
private Node findNode(int index){
int i = 0;
for (Node p = head; p != null ;p = p.next , i++){
if (index == i){
System.out.println("index的索引为" + i);
return p;
}
}
return null;//没找到
}
//get方法
public int get(int index){
//node是指定索引的节点
Node node = findNode(index);
if (node == null){
//抛出异常
//String.format表示字符串中有一些东西是可变的
throw new IllegalArgumentException(String.format("index[%d] 不合法%n",index));
}
return node.value;
}
比如上例:别人调用只需要通过get传入index(索引)获得链表的值,并不需要知道你通过findNode()怎么获得该索引的链表对象
2.继承
2.1 继承的好处
1.继承基类(父类)的方法,并做出自己的改变和扩展
2.子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需要扩展自己个性化的
进行一下简单的介绍:继承是从已有的类.新的类能吸收已有类的数据属性和行为,并能扩展新的能力.在本质是上特殊~一般的关系:
比如:猫类、狗类、虎类中可以抽象出一个动物类,具有和猫、狗、虎、类的共同特征(吃、跑、叫等)。
2.2 什么不能被继承
通过extends关键字来实现继承,private和static定义的变量和方法不可以可以被继承。
final修饰的类也不可以被继承,子类不能继承父类的构造方法。
2.3 super关键字
2.3.1 虽然子类不能继承父类的构造方法,但是需要先帮父类完成构造,才能构造自己!!
子类对象的实例化之前,必须在子类的构造方法中,调用父类的构造方法
2.3.2 通过super(...)调用
2.3.3 当父类有无参构造方法时,super()可以省略
2.3.4 通过super可以明确访问父类中的属性和方法(有访问权限的前提下)
public class Dog extends Animal{
public void show (){
//调用父类的成员方法
super.eat();
//调用父类的成员属性
String dogName = super.name;
}
}
比如,上例的狗类可以通过super关键字来调用父类的成员属性(name) 成员方法(eat)
总结super关键字:
就是你在子类中仍需要用到父类的东西时候,就需要用super关键字而super调用构造方法是必然的,因为你在给子类构造实例的时候必须通过super先给父类实例
2.4 this关键字
2.4.1.区分局部变量和成员变量
public class Actor {
private String name;
private int age;public Actor(String name, int age) {
name = name;
age = age;
}}
此时你的name = name :虚拟机就会使用就近原则进行赋值让你的name自己给自己赋值(局部变量给局部变量赋值)而你在调用name的时候就是null
所以为了解决这个问题:此时我们就需要引用this 关键字
public class Actor {
private String name;
private int age;public Actor(String name, int age) {
this.name = name;
this.age = age;
}}
此时this.name就表示成员变量(定义在类中的变量),而后面的name通过就近原则就是局部变量(定义在方法中的变量)
2.4.2.this是一个引用
this表示方法调用者的地址值
public class Actor {
private String name;
private int age;public Actor(String name, int age) {
this.name = name;
this.age = age;
}
public void show(){
System.out.println(this.name);
}
public class Test {
public static void main(String[] args) {
Actor a1 = new Actor("张三",19);
Actor a2 = new Actor("李四",19);
a1.show();
a2.show();
}
}
此时如果用a1调用show()方法,this引用的地址就是a1所以说show方法中的this.name就等价于a1.name
补充知识点:
Object 是所有类的祖先类(没有任何属性)
如果一个类没有出现extends关键字,则这个类会默认继承Object。
因为所有类都直接或者间接继承于Object,因为基本你存在extends但是你迟早有一个父类会没有extends关键字的。
父类和子类的关系相当于“是一个(is a)”的关系,即子类是父类的一个对象,而不是“有(has)”的关系,而这个has关系就是我们下面学的接口的实现了。
3.多态
对于多态的个人见解:我认为相比于封装和继承,多态是三大特征中比较难的一个,封装和继承最终归结于多态,多态是指的类和类的关系,两个类由继承关系,存在方法重写,故而可以调用时有父类引用指向子类对象,多态必备的三个要素:继承,重写,父类引用指向子类对象
3.1 多态的好处
3.1.1.基于对象所属类的不同,调用同一个重写的方法,表现的行为不一样,这种思想,叫做多态!!
比如:猫和狗都调用了say()(叫)方法,狗是汪汪汪,猫是喵喵喵
3.1.2.代码更加灵活:无论右边new的时候换成哪个子类对象,等号左边调用的方法都不会变化
因为调用方法:编译看左边,运行看右边(我在下面将会讲解)
3.1.3.提高程序的扩展性:定义方法的时候,使用父类类型作为参数,将来使用时,使用具体的子类类型操作
public class Target {
public static void main(String[] args) {
Cat c = new Cat();
Dog d = new Dog();
Elephant e = new Elephant();
eat(c);
}
public static void eat(Animal animal){
animal.eat();
}
}
上例:你不想让猫吃了 ,只需要在eat()方法里面换一个参数即可
3.1.4..能够降低代码的“圈复杂度”避免使用大量的 if else代码
public static void drawMaps1() {
Cycle cycle = new Cycle();
Rect rect = new Rect();
Flower flower = new Flower();
String[] strings = {"cycle", "rect", "cycle", "rect", "flower"};
for (String s : strings) {
if (s.equals("cycle")) {
cycle.draw();
} else if (s.equals("rect")) {
rect.draw();
} else {
flower.draw();
}
}
}
这样就会有大量的if else 而我们应该怎么优化呢此时就用到了我们的多态public static void drawMaps() {
Cycle cycle = new Cycle();
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = {cycle,rect,cycle,rect,flower,triangle};//发生了向上转型
for(Shape shape : shapes) {
shape.draw();
}
}
这样优化是不是简便了许多
3.2多态的弊端(会涉及向下转型和instanceof关键字)
3.2.1.属性没有多态性(包含俩个顺口溜)
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
这也就是那个顺口溜,调用成员变量,编译看左边运行看左边,
这句话的意思就是,代码报不报错就看左边有没有这个成员方法,运行的结果也是看左边的成员方法的值
比如:
Animal a2 = new Dog();
System.out.println(a2.name);
编译通不通过就看Animal类中有没有name这个变量有就不报错,没有就报错,而运行的结果还是看Animal的name值此外还有有另一个顺口溜就是,调用成员方法,编译看左边运行看右边。
这句话的意思就是,代码报不报错就看右边有没有这个成员方法,运行的结果就看右边的成员方法的值-------> 可以这样理解吧:看子类有没有重写父类的这个方法进行覆盖,如果没有覆盖就是父类的成员方法 。
public class Test {
public static void main(String[] args) {
Person a1 = new Student("张三",19);
a1.show();
}
}
编译通不通过就看Person类中有没有show这个方法有就不报错,没有就报错,而运行的结果还是看Student的show()方法,如果Student没有show()方法,就看Person的show()方法
3.2.2.构造方法没有多态性
class B {
public B() {
// do nothing
func(); // 会调用子类的func
}
public void func() {
System.out.println("B.func()");
}
}class D extends B {
private int num = 1;
D() {
super();
}
@Override
public void func() {
//0
System.out.println("D.func() " + num);
}
}
public class Test2 {
public static void main(String[] args) {
D d = new D();
}
}
比如这道题答案是什么呢?如果你的答案对了你就理解了构造方法没有多态性了
答案:是输出的是D.func()0 你对了吗?
思路分析:为什么输出0?
是因为父类还没有走完子类并不会赋值
super()走子类的构造方法,因为调用方法时候还父类的构造方法还没有没有走完,所以并不会让
成员变量赋值成功!!
但是这样的代码不建议写,尤其是在父类构造方法中调用子类方法
构造子类的同时,会调用子类的构造方法
所以如果属性也具有都多态,num的值就会是1
3.3.不能调用子类特有的方法
个人见解:我认为子类最大的弊端就是不能调用子类特有的方法。
此时调用Bird类特有的fly方法就会报错,让你在Animal类中创建fly方法
解决方案:就是通过向下转型将其转换为Bird
public class Target {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Bird();
Bird b = (Bird) a2;
b.fly();
}
}
通过向下转型即可解决上述问题,但是又会出现新的问题,向下转型并不安全。为什么呢?请跟我看下例public class Target {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Bird();
Bird b = (Bird) a1;
b.fly();
}
}
狗会飞吗?通过上述代码肯定会,但是代码会有bug,这就是不安全的原因此时解决这个新的问题,就会涉及到一个关键字instanceof。
public class Target {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Bird();
if (a1 instanceof Bird) {
Bird bird = (Bird) a1;
bird.fly();
} else {
System.out.println("不能飞");
}
}
此时就会通过 if (a1 instanceof Bird) 表示判断a1对象是不是表示为Bird类,是进入代码进行强转,不是就不会进入。
4.重写
4.1.重载和重写区别
重写:方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写
重载:同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
4.2.重写的意义
设置子类重写的意义,一般都是父类方法满足不了自己了,自己想要进行扩充
比如这个打电话,若干年后,需要增加新的功能(显示地区,显示姓名),就需要重写来电显示这个方法进行重写(覆盖),而如果原有的显示号码你扔需要使用就只需要通过super.来电显示方法即可,使用父类中的方法。
4.3.重写的前提条件(什么东西可以重写)
(1).被private修饰的方法 不能进行重写
(2).被static修饰的方法 不能进行重写
(3).被final修饰的方法不能进行重写,此时这个方法,被叫做密封方法
(4).访问修饰限定符 private < 默认权限 < protected < public
(5).方法的返回值可以不同,但是必须是父子类关系
(6).构造方法 不能发生重写
4.4.向上转型
有三种方式可以发生向上转型
4.4.1.直接赋值
//父类引用 引用了子类对象
Animal a1 = new Cat();
Animal a2 = new Dog();
Animal a3 = new Elephant();
4.4.2.方法传参的方式.
public class Target {
public static void main(String[] args) {
Cat cat = new Cat();
eat(cat);
}
public static void eat(Animal animal){animal.eat();
}}
4.4.3.返回值
public static Animal func(){ return new Dog();
}
三.总结
- 面向对象编程是利用类和对象编程的一种思想。
- 正所谓万物可归类,对象是具体的世界事物,类是对于世界事物的高度抽象,不同的事物之间有不同的关系。
- 一个类自身与外界的封装关系,一个父类和子类的继承关系,一个类和多个类的多态关系。
- 面向对象的三大特征封装,继承,多态。
- 封装,封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;
- 继承是父类和子类的关系。
- 多态说的是类与类的关系。