面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么
比如:洗衣机洗衣服
面向过程会将任务拆解成一系列的步骤(函数),1、打开洗衣机---->2、放衣服---->3、放洗衣粉----->4、清洗----->5、烘干
面向对象会拆出人和洗衣机两个对象:人:打开洗衣机放衣服放洗衣粉洗衣机:清洗│烘干
从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护
封装
封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项内部细节对外部调用透明,外部调用无需修改或者关心内部实现,隐藏了类的内部实现机制,对外界而言它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
封装的体现:
1、 javabean的属性私有,提供get,set方法对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定。
而不能由外部胡乱修改,该name有自己的命名规则,明显不能由外部直接赋值(比如下面的例子,对于每一个name,内部都会把它修改成 “tuling_”+name)
private string name ;
public void setName (string name){this.name = "tuling_"+name;}
2、orm框架
操作数据库,我们不需要关心链接是如何建立的、sq|是如何执行的,只需要引入mybatis,调方法即可
而面向过程的话,这些都要自己写函数实现
继承
继承基类的方法,并做出自己的改变和/或扩展,很多时候是为了复用代码,子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
继承所描述的是“is-a”的关系,如果有两个对象A和B,若可以描述为“A是B”,则可以表示A继承B,其中B是被继承者称之为父类或者超类,A是继承者称之为子类或者派生类。
同时在使用继承时需要记住三句话:
1、子类拥有父类非private的属性和方法。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。
构造器只能够被调用,而不能被继承。 调用父类的构造方法使用super()即可。
对于子类而已,其构造器的正确初始化是非常重要的,而且当且仅当只有一个方法可以保证这点:在构造器中调用父类构造器来完成初始化,而父类构造器具有执行父类初始化所需要的所有知识和能力。
public class Person {
protected String name;
protected int age;
protected String sex;
Person(){
System.out.println("Person Constrctor...");
}
}
public class Husband extends Person{
private Wife wife;
Husband(){
System.out.println("Husband Constructor...");
}
public static void main(String[] args) {
Husband husband = new Husband();
}
}
Output:
Person Constrctor...
Husband Constructor...
通过这个示例可以看出,构建过程是从父类“向外”扩散的,也就是从父类开始向子类一级一级地完成构建。而且我们并没有显示的引用父类的构造器,这就是java的聪明之处:编译器会默认给子类调用父类的构造器。
但是,这个默认调用父类的构造器是有前提的:父类有默认构造器。如果父类没有默认构造器,我们就要必须显示的使用super()来调用父类构造器,而且必须是在子类构造器中做的第一件事(第一行代码)。否则编译器会报错:无法找到符合父类形式的构造器。
当父类有显式的构造函数时,子类也应该有构造函数
点击 Create constructor matching super;
class Tiger extends Animal{
public Tiger(String name) {
super(name);
}
}
子类无法继承父类的私有成员
将子类转换成父类,在继承关系上面是向上移动的,所以一般称之为向上转型。由于向上转型是从一个叫专用类型向较通用类型转换,所以它总是安全的,唯一发生变化的可能就是属性和方法的丢失(子类中的某些属性和方法,父类中没有)。这就是为什么编译器在“未曾明确表示转型”或“未曾指定特殊标记”的情况下,仍然允许向上转型的原因。
继承的缺陷
1、父类变,子类就必须变。
2、继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。
3、继承是一种强耦合关系。
解决方法:如果必须向上转型,则继承是必要的,但是如果不需要,则应当好好考虑自己是否需要继承。
多态
基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。多态的三个条件是继承,方法重写,父类引用指向子类对象
所谓多态就是指程序中 定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:
酒 a = 剑南春
酒 c = 酒鬼酒
…
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。
父类类型变量名= new 子类对象;
变量名.方法名();//调用的是子类的方法
多态的好处:对于上面的代码,如果我们想换成其他子类,只需要改new后边的子类就可以了,对于调用的代码不许要做任何改变,易于程序维护和扩展
父类类型变量名= new 子类对象2;
变量名.方法名();//调用的是子类的方法
多态的弊端:无法调用子类特有的功能,即调用的方法必须在父类中存在,调用的方法属于方法重写
我们首先编写父类Animal类,有Crawl和Roar方法,以及属性name,默认是default,父类其实相当于抽象出一个类的共同行为,当我们编写Monkey类继承了Animal,那么Monkey也会得到Crawl(), Roar()的行为,以及name属性,同时可以扩展Climb()这一行为,这是Monkey相对于Animal类独有的方法/行为
当我们编写Tiger类继承了Animal,那么Tiger类也会得到Crawl(), Roar()的行为,以及name属性,同时可以扩展run()这一行为,这是Tiger类相对于Animal类独有的方法/行为
class Animal{
String name="Animal";
void Crawl()//爬行
{
System.out.println("Animal Can Crawl");
}
void Roar()//咆哮
{
System.out.println("Animal Can Roar");
}
}
class Monkey extends Animal{
String name="Monkey";
void Climb(){//爬树
System.out.println(" Monkey Can Climb");
}
void Roar()//咆哮
{
System.out.println("Monkey Can Roar");
}
void Crawl()//爬行
{
System.out.println("Monkey Can Crawl");
}
}
class Tiger extends Animal{
String name="Tiger";
void run(){//奔跑
System.out.println(" Tiger Can run");
}
void Roar()//咆哮
{
System.out.println("Tiger Can Roar");
}
void Crawl()//爬行
{
System.out.println("Tiger Can Crawl");
}
}
Animal animal=new Monkey("猴子");
animal.Climb();
可以看到,无法调用Climb方法,这是因为Animal类没有Climb方法,只有Roar和Crawl方法,因为Animal是父类,在编译的时候animal.Climb(),首先会检查Animal类有没有Climb方法,没有则编译不通过
测试
public class ObjiectOrientedTest {
public static void main(String[] args) {
Animal animal=new Animal();
animal.Roar();
animal.Crawl();
System.out.println(animal.name);
Animal monkey=new Monkey();
monkey.Crawl();
monkey.Roar();
System.out.println(monkey.name);
Animal tiger=new Tiger();
tiger.Crawl();
tiger.Roar();
System.out.println(tiger.name);
}
}
结果
可以看到,因为Monkey和Tiger都重写了父类Animal的Roar和Crawl方法,所以在调用时子类都是执行了各自的方法,但是打印出来的name都是Animal
Animal Can Roar
Animal Can Crawl
Animal
Monkey Can Crawl
Monkey Can Roar
Animal
Tiger Can Crawl
Tiger Can Roar
Animal
现在,我们把父类的name 和 Crawl() 都编程static的,再看看结果
class Animal{
static String name="Animal";
static void Crawl()//爬行
{
System.out.println("Animal Can Crawl");
}
void Roar()//咆哮
{
System.out.println("Animal Can Roar");
}
}
结果:
多态:Percent p = new Children();
对于成员方法:编译看左边,运行看右边。
对于成员变量:编译运行都看左边。也就是成员变量没有多态特性。
静态方法和变量:编译运行都看左边,同成员变量一样。
子类的同名的 类变量、静态方法、静态变量 不会覆盖父类。 调用p的这些属性找的是父类的属性
子类同名的类方法覆盖父类。 调用p的方法找的是子类的方法。
调用p的属性(变量或方法)会检查父类Percent是否存在此属性,如果不存在(只有子类有)或者父类属性用private修饰则编译不通过。
如果把父类Animal的Roar方法设置为private
private void Roar()//咆哮
{
System.out.println("Animal Can Roar");
}
可以看到,无法调用
如果我们这样
Monkey monkey=new Monkey();
monkey.Crawl();
monkey.Roar();
System.out.println(monkey.name);
Tiger tiger=new Tiger();
tiger.Crawl();
tiger.Roar();
Monkey Can Crawl
Monkey Can Roar
Monkey
Tiger Can Crawl
Tiger Can Roar
Tiger
注意,因为父类的Crawl方法已经是私有的了,子类无法继承到,所以,子类虽然有Crawl方法,但是不能叫重写了,只不过是与父类有同名的方法而已,所以Crawl方法也谈不上多态了
注意:父类中的方法不是静态的话,子类也不能加static,父类中是static,子类也必须加static
多态有两种,一种是子类具有同父类相同名称,相同参数的方法,这样子类的方法会覆盖父类的方法,称为方法的重写。一种是同一个类里具有相同名称,不同参数的方法,称为类的重用
。使用多态能使代码具备可替换性、灵活性、可扩充性、接口性、简化性等优点。
现在把Tiger类的Crawl方法改一下
void Crawl(int a)//爬行
{
System.out.println("Tiger Can Crawl"+a);
}
我们发现,并不能实现调用有参数的Crawl,因为父类的Crawl是没有参数的
现在
Animal tiger=new Tiger();
tiger.Crawl();
tiger.Roar();
看看输出
Animal Can Crawl
Tiger Can Roar
看到了吗,在类的重用的情况下,调用的是父类的方法
推荐阅读
java提高篇(二)-----理解java的三大特性之继承
java提高篇(四)-----理解java的三大特性之多态
多态问题:编译看左边,运行看右边 是什么意思?