对比面向过程,是有两种不同的处理问题的角度的
面向过程更注重处理事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象),及各自需要做什么?
比如:洗衣机洗衣服
面向过程:会将任务拆解成一系列的步骤(函数)/(方法),
1. 打开洗衣机-----> 2.洗衣服------> 3.放洗衣粉------> 4.清洗------> 5.烘干
面向对象:会拆出人和洗衣机两个对象:
人:打开洗衣机 放衣服 放洗衣粉(人可以称为类)(打开洗衣机 放衣服 放洗衣粉就是类中的方法)
洗衣机: 清洗 烘干(只需要把衣服为参数传给洗衣机即可)
从以上例子可以看出:面向过程比较高效,而面向对象更易于复用,扩展和维护
1.,在于明确标识出允许外部使用的所有成员方法,可以在不影响使用的情况下改变类的内部结构,同时保护了数据
2.内部细节对外部调用透明,外部调用无需修改或者关心内部实现
3.对于外界它的内部细节是隐藏的,暴露给外界的只能是它的访问方法
使用者只能通过事先定制好的方法来访问数据,可以方便地加入逻辑控制,限制对属性的,不合理操作
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岁的吧,所以就要用封装,不让别人乱给你定女朋友年龄
使用者按照既定的方式调用方法,不必关心方法内部的实现,便于使用;便于修改,增强代码的可维护性;
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()怎么获得该索引的链表对象
1.继承基类(父类)的方法,并做出自己的改变和扩展
2.子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需要扩展自己个性化的
进行一下简单的介绍:继承是从已有的类.新的类能吸收已有类的数据属性和行为,并能扩展新的能力.在本质是上特殊~一般的关系.
比如:猫类、狗类、虎类中可以抽象出一个动物类,具有和猫、狗、虎、类的共同特征(吃、跑、叫等)。
通过extends关键字来实现继承,private和static定义的变量和方法不可以可以被继承。
final修饰的类也不可以被继承,子类不能继承父类的构造方法。
1.虽然子类不能继承父类的构造方法,但是需要先帮父类完成构造,才能构造自己!!
子类对象的实例化之前,必须在子类的构造方法中,调用父类的构造方法
1.1通过super(...)调用
1.2当父类有无参构造方法时,super0可以省略
2. 通过super可以明确访问父类中的属性和方法(有访问权限的前提下)
public class Dog extends Animal{ public void show (){ //调用父类的成员方法 super.eat(); //调用父类的成员属性 String dogName = super.name; } }
比如,上例的狗类可以通过super关键字来调用父类的成员属性(name) 成员方法(eat)
就是你在子类中仍需要用到父类的东西时候,就需要用super关键字
而super调用构造方法是必然的,因为你在给子类构造实例的时候必须通过super先给父类实例
居然说了super关键字,那么this关键字肯定是必不可少的
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.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关系就是我们下面学的接口的实现了。
对于多态的个人见解:我认为相比于封装和继承,多态是三大特征中比较难的一个,封装和继承最终归结于多态,多态是指的类和类的关系,两个类由继承关系,存在方法重写,故而可以调用时有父类引用指向子类对象,多态必备的三个要素:继承,重写,父类引用指向子类对象
1.基于对象所属类的不同,调用同一个重写的方法,表现的行为不一样,这种思想,叫做多态!!
比如:猫和狗都调用了say()(叫)方法,狗是汪汪汪,猫是喵喵喵
2.代码更加灵活:无论右边new的时候换成哪个子类对象,等号左边调用的方法都不会变化
因为调用方法:编译看左边,运行看右边(我在下面将会讲解)
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()方法里面换一个参数即可
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(); } }
这样优化是不是简便了许多
1.重载和重写不同
重写:方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写
重载:同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
重写就相当于图一,俩个箭绑一起将后面的箭进行覆盖
重载就相当于图二,每个箭都是独立的个体都有独自的作用
2.重写的意义
设置子类重写的意义,一般都是父类方法满足不了自己了,自己想要进行扩充
比如这个打电话,若干年后,需要增加新的功能(显示地区,显示姓名),就需要重写来电显示这个方法进行重写(覆盖),而如果原有的显示号码你扔需要使用就只需要通过super.来电显示方法即可,使用父类中的方法。
3.重写的前提条件(什么东西可以重写)
(1).被private修饰的方法 不能进行重写
(2).被static修饰的方法 不能进行重写
(3).被final修饰的方法不能进行重写,此时这个方法,被叫做密封方法
(4).访问修饰限定符 private < 默认权限 < protected < public
(5).方法的返回值可以不同,但是必须是父子类关系
(6).构造方法 不能发生重写
有三种方式可以发生向上转型
1.直接赋值
//父类引用 引用了子类对象 Animal a1 = new Cat(); Animal a2 = new Dog(); Animal a3 = new Elephant();
2.方法传参的方式.
public class Target { public static void main(String[] args) { Cat cat = new Cat(); eat(cat); } public static void eat(Animal animal){ animal.eat(); } }
3.返回值
public static Animal func(){ return new Dog(); }
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()方法
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.不能调用子类特有的方法
个人见解:我认为子类最大的弊端就是不能调用子类特有的方法。
此时调用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类,是进入代码进行强转,不是就不会进入。
面向对象编程是利用类和对象编程的一种思想。
正所谓万物可归类,类是对于世界事物的高度抽象,不同的事物之间有不同的关系。
一个类自身与外界的封装关系,一个父类和子类的继承关系,一个类和多个类的多态关系。
正所谓万物皆对象,对象是具体的世界事物。
面向对象的三大特征封装,继承,多态。
封装,封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;
继承是父类和子类的关系。
多态说的是类与类的关系。