面向对象特性
面向对象是一种编程思想,其中具有这样思想的程序通常会存在:封装、继承、多态的三个特征。面向对象会采用抽象的方式将一类事物的共性进行提取,制作成一个类模板,再通过类模板来创建具有具体特征的实例。
类实例之间也会存在融合和共通的关系,这方便了在开发过程中合理的安排数据使用途径和提高代码重用性。
封装
封装存在两个说法:
- 将一类实例共性抽取并使用程序(一般是类)的形式描述出来。
- 将类中的各成员(属性和方法)进行私有化控制,并提供可访问的途径进行间接的内容访问。
第一种描述实际上就是在上文中对各类事物共性抽取之后用类描述的过程,第二种是在开发过程中经常用到的写法,以下介绍关于第二种描述的具体实现。
私有化
在学习第二种方法之前,需要简单学习一部分访问修饰符。修饰符也就是来修饰某个东西的,被修饰的东西会有一部分特殊的效果。例如访问修饰符是用来控制某个东西的访问范围,也就是在哪里可以调用:
public:公开的,开放的。被修饰的属性或方法可以在任意地方通过引用调用使用。
private:私有的。被修饰的属性或方法只能在对象内部调用使用,不能在外部通过引用调用使用。
借助访问修饰符,就可以将属性进行私有化的封装,将属性的使用范围约束到对象里,不能在外部直接调用属性:
class Dog{
private String xingGe;
}
在对属性进行私有化控制了以后,在对象的外部就无法通过引用来调用属性了:
Dog d = new Dog();
d.xingGe = "活泼的"; //报错,提示无法访问
但在对象的内部是可以进行调用使用的,体现在类里如下:
class Dog{
private String xingGe;
public void test(){
this.xingGe = "内向的";
}
}
提供访问方法
在私有化属性控制了以后,外部就无法对属性进行存取值操作了,那就要提供可以间接访问属性的方法。
虽然属性不能在外部访问了,但是可以在对象的内部进行访问。那如果在对象内部添加可以对外访问的方法来对属性进行控制即可:
class Dog{
private String xingGe;
public void setXingGe(String xingGe){
this.xingGe = xingGe;
}
public String getXingGe(){
return xingGe;
}
}
上文中的get和set方法是对外开放的,可以在外部进行调用。并且因为方法中实际操作的是对象中私有的属性,也就可以间接的操作私有属性了:
Dog d = new Dog();
d.setXingGe("内向的");
String xingGe = d.getXingGe();
get方法通常用于获取属性值,没有参数只需要设置返回值即可。set方法用于对属性进行赋值,没有返回值只有参数。
get和set方法的命名是有规范的,通常使用get或set加上属性首字母大写后的单词。在未来书写描述某一类事物的类的时候,就要将属性进行封装,并且一定要提供get和set方法。
如果提供了get和set方法之后,可以间接的访问属性,那为什么不直接访问属性呢?看起来好像并没有什么保护啊。
属性通常是值,并不具有流程或者计算的功能。但是方法能够在对属性赋值或者取值的时候进行运算等操作,可控的将属性返回和赋值,通过方法间接层中的代码流程,对属性进行保护。
继承
继承也就是让类之间存在子父的包含关系,子类父类之后,可以包含到父类中所有非私有的属性和方法。
Java是一个单继承的语言,每一个子类都只能有一个父类,一个父类可以存在多个子类。在项目设计中,可以将子类中的共性抽取到父类中,从而减少每个子类中的代码,提高父类中的属性和方法的重用性。父类一般是基础信息的概括,子类将在父类基础数据之上添加个性的属性和方法。
构建继承
在子类中,使用extends来指明当前子类的父类,或者理解为当前类包含那个类的内容。同一个类可以被多个类继承,但一个类只能有一个父类。下文中生物类将拥有动物和植物两个子类,动物类又具有猫和狗两个子类:
class ShengWu{}
class ZhiWu extends ShengWu{}
class Animal extends ShengWu{}
class Dog extends Animal{}
class Cat extends Animal{}
方法与属性的继承
在继承了父类之后,就可以使用父类中非私有的属性和方法。下文中创建父类动物类:
class Animal{
String name;
String sex;
String color;
String age;
public void run(){
System.out.print("动物都有新陈代谢");
}
}
动物类描述了所有动物都会具有的属性和方法,下文中创建动物类的两个子类:
class Dog extends Animal{
public void chiGuTou(){
System.out.print("狗狗都爱吃骨头");
}
}
class Cat extends Animal{
public void zhuoLaoShu(){
System.out.print("猫都会捉老鼠");
}
}
狗和猫类继承了动物类,虽然看起来只有一个方法,但实际上已经拥有了动物类中所有非私有的属性和方法。在创建狗类或者猫类对象之后,就可以使用引用调用父类中的方法:
public class Index{
public static void main(String[] args){
Dog d = new Dog();
d.name = "旺财";
System.out.print(d.name);
d.run();
d.chiGuTou();
}
}
访问修饰符
在封装中,用到了两个访问修饰符。public表示可以在任何地方使用的修饰符,也是访问范围最大的修饰符。private表示只能在当前对象中使用,是访问范围最小的修饰符。
访问修饰符控制的访问范围有:类中、包内、继承体系中。访问修饰符一共有四个,可以分别在这三个方向分析他们的访问范围:
修饰符的名字 | 修饰符 | 访问区域 |
---|---|---|
公开的 | public | 所有的包和所有的类 |
受保护的 | protected | 本包和不是本包的子类 |
友好的 | (friendly) | 当前包 |
私有的 | private | 本类 |
访问修饰符通常在类里修饰属性和方法,public是可以修饰类的。protected表示可以在同一包中,或者是当前类的子类可以使用。在类中不写修饰符就是friendly修饰。
访问修饰符 | 本类 | 本包 | 不是本包的子类 | 不是本包也不是子类 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
(friendly) | √ | √ | ||
private | √ |
方法的重写Override
当存在子父类继承关系的时候,如果子类和父类中方法名相同但是参数表不同的方法将自动重载到子类中,但是如果存在方法名和参数表相同的方法呢?
实际上当子父类存在方法名和参数表一样的方法的时候,调用方法时会自动调用子类中的方法。这并不代表父类中的方法消失了,而是子类的优先级高于父类,也可以称为:子类会覆盖父类中同名同参的方法。
实际上子类在去覆盖父类中方法的时候还有访问修饰符上的要求,子类的访问修饰符可访问的范围必须大于等于父类中的访问修饰符,例如父类中的同名同参方法的访问修饰符是public,那子类中必须存在一个跟public相同或者比其访问范围更大的方法,因为public是最大范围的,所以子类中只能写成public。
当程序进行编译时,会判断子类中是否存在与父类同名的方法,如果存在同名方法,则表示此方法可能会发生重载,也可能发生重写。当发现参数表不同时,则表示两个方法会发生重载,不会再去判断方法的访问修饰符是否符合要求。如果两个方法的参数表完全相同,则表示方法将要进行重写,那这种情况下就必须保证访问修饰符符合要求,并且返回值要完全相同,否则会编译时报错。
构建重写的代码:
class Animal{
String name;
String sex;
String color;
String age;
//此jiao方法,将被子类覆盖(重写)掉
public void jiao(){
System.out.print("会叫");
}
}
class Dog extends Animal{
public void chiGuTou(){
System.out.print("康驰康驰");
}
//此jiao方法,将重写父类中同名,同参,同返,访问修饰符相同或更窄的方法
public void jiao(){
System.out.print("汪汪");
}
}
重写与重载的区别
重载发生在一个类中,即使字符类中出现了重载的情况,也可以理解为子类继承到父类的方法后在子类中发生了重载。
重载是在一个类中存在方法名相同,参数表不同的方法,可以通过传入不同的参数来调用不同的方法。重写表示在子父类中存在方法名、参数表、返回值、访问修饰符符合要求的方法,调用时会启动子类的方法。
运行时的多态是重写,编译时的多态是重载。
super关键字
与this关键字类似,this关键字表示当前对象的地址中的内容,而super关键字则表示父类中的内容。
this可以在当前类中书写,并调用对象中的属性、方法、构造方法,super同样是写在类中,在对象中调用父类的属性、方法、构造方法。
当子父类中存在同名的属性时,可以采用super来指定父类实例的属性,使用this来指定当前类实例的属性。
调用父类属性:
public class Index{
public static void main(String[] args){
Dog d = new Dog();
d.name = "xiaoming"; //赋值给Dog类中的name属性
d.setName("xiaohong"); //赋值给Animal类中的name属性
String name = d.getName();
System.out.print(name);
}
}
class Animal{
String name;
}
class Dog extends Animal{
String name;
public void setName(String name){
super.name = name;
}
public String getName(){
return super.name;
}
}
调用父类方法:
public class Index{
public static void main(String[] args){
Dog d = new Dog();
d.eat();
}
}
class Animal{
public void eat(){
System.out.print("吃就不会死");
}
}
class Dog extends Animal{
public void eat(){
super.eat();
System.out.print("最喜欢吃骨头");
}
}
调用父类构造方法:
在对象中指定调用父类构造方法的语句,必须写在当前对象构造方法的第一行。
并且每一个子类的每一个构造方法中,都默认存在对父类默认无参构造方法的调用。也就是说如果父类中没有默认的无参构造方法,就要声明出来,或者在所有子类的所有构造方法第一行指定父类的有参构造方法。
public class Index{
public static void main(String[] args){
Dog d = new Dog("tom",8);
}
}
class Animal{
String name;
int age;
public Animal(String name,int age){
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
System.out.print(this.name);
System.out.print(this.age);
}
}
测试对父类构造方法的调用:
//测试:默认对父类构造方法的调用
class Animal{
String name;
int age;
public Animal(int a){
System.out.print(123);
}
}
class Dog extends Animal{
public Dog(){
super(1);//如果父类中没有无参构造方法,则要手动的调用有参的构造方法!
}
}
更新创建对象的过程
在存在子父类关系的时候,子类创造对象前要先对父类进行对象的创建,当父类创建完成后,子类才能完整的包含父类的内容进来:
- 给子父类的属性同时开辟空间赋默认值。
- 先给最高类(父类)赋初始值,调用父类的构造方法。
- 然后递归的往低类(子类)赋初始值,调用子类的构造方法。