本文主要针对面向对象的三大特征:继承、封装、多态进行详细的总结。
从英文字面意思理解,extend的意思是”“扩展”,继承让我们更加容易实现类的扩展,继承让子类继承了父类的特征和行为,实现了代码的重用,不用再重新发明轮子。
继承符合的关系符合的逻辑:“XX是一种XX”,只要这种东西能说通,就是一种继承关系。
如图:哺乳动物是一种动物,这能说得通,说明哺乳动物是从动物继承过来的;猫;是一种哺乳动物,这也能说得通,说明猫是从哺乳动物继承过来的。
//示例 public class Test { public static void main(String[] args) { Student s = new Student(18,"芮耳朵","Java"); s.study();//继承了父类Person中的方法 s.play(); } } class Person { int age; String name; public void study() { System.out.println("每天进步一点点!"); } } class Student extends Person { String major; public void play() { System.out.println("我喜欢打篮球"); } //构造方法 public Student(int age, String name, String major) { //拥有父类的所有属性 this.age = age; this.name = name; this.major = major; } } /*运行结果是:喜欢Java,每天进步一点点! 我喜欢打篮球*/ 复制代码
instanceof是双元运算符,中文解释为“...的例子”,左边的操作元是对象,右边是一个类;当左边的对象是右面的类创建的对象时,返回true,否则返回false
//示例 public class Test { public static void main(String[] args) { Student s = new Student(18,"芮耳朵","Java"); System.out.println(s instanceof Person); System.out.println(s instanceof Student); } } //运行结果都是true。复制代码
方法的重载是指一个类中可以定义多个方法名相同,但参数不同的方法。 调用时,会根据不同的参数自动匹配对应的方法。
重载的方法,实际是完全不同的方法,只是名称相同而已!
构成条件:形参类型、新参个数、新参顺序不同
//示例 public static int add(int n1, int n2) 与 public static int add(int n1, int n2, int n3) //方法名相同,参数个数不同,构成重载。 public static int add(int n1, int n2) 与 public static double add(double n1, int n2) //方法名相同,参数类型不同,构成重载。 public static int add(int n1, int n2) 与 public static double add(int n1,double n2) //方法名相同,参数顺序不同,构成重载。复制代码
//示例:只有返回值不同不构成方法的重载 public static int add(int n1, int n2) 与 public static double add(int n1, int n2) //只有返回值不同,不构成重载。 public static int add(int n1, int n2) 与 public static int add(int num1,int num2) //只有参数名称不同,不构成重载。复制代码
Object类是所有Java类的根基类,所有的Java对象都拥有Object类的属性和方法。Java只支持单继承,子类只能继承一个父类,父类再有父类也只能有一个,Java为方便组织类,提供了一个最根上的类,相当于所有的类都是从这个类继承而来,就是Object类。 如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类。
这里介绍下 什么是哈希编码 ,后面会涉及到。
一个程序运行的时候,可能会有很多的对象在内存里分配,对于虚拟机来说,它运行的时候需要找到这些对象的地址,Java虚拟机会用一张表纪录每一个对象在什么位置上,这张表一般是使用哈希编码来记录,每个对象都有自己唯一的哈希编码,体现了一种数据内容和数据存放地址之间的映射关系。但是Java本身对哈希编码的实现有点问题,它可能有两个对象,内容不同,但是两者的哈希编码可能是一样的,不同的对象计算哈希编码的时候,可能会引起冲突,所谓的哈希冲突。
Object类方法
一个字符串和另外一个类型连接的时候,另外一种类型会自动转换成String类型,然后和字符串连接。 基础类型数据int,float,double转换成字符串比较简单,按照它们的数字转换即可,要是引用类型怎么办?Student s1 = new Student;一个字符串加上这个s1,你就不知道要怎么把这个p转换成字符串了,因为s1是一个引用类型。这时候toString方法就有用了。
//toString方法:该方法在打印对象时被调用,将对象信息变为字符串返回,默认输出对象地址 //toString方法源码: public String toString() { return getClass().getName() + "@" + Interger.toHexString(hashCode()); //默认会返回"类名+@+16进制的hashcode"。 //在打印或者用字符串连接对象时,会自动调用该对象的toString()方法。 } //示例:toString方法未重载之前 class Person { int age; String name; public class Test { public static void main(String[] args) { Person p = new Person(); p.age = 18; p.name = "芮耳朵"; //自动调用了toString()方法,简写。 System.out.println(p); //不简写 System.out.println(p.toString()); } } /*运行结果:Person@2a84aee7 Person@2a84aee7 */ --------------------------------------------------- //示例:toString重载之后 class Person { int age; String name; @Override public String toString() { return name+",年龄:"+age; } } public class Test { public static void main(String[] args) { Person p = new Person(); p.age = 18; p.name = "芮耳朵"; System.out.println(p); System.out.println(p.toString()); } } /*运行结果:芮耳朵,年龄:18 芮耳朵,年龄:18 */复制代码
在Java类中,当new一个对象出来的时候,这个对象会产生一个this的引用,这个this引用指向对象本身。如果new出来的对象是一个子类对象的话,那么这个子类里面还有一个super引用,这个super指向当前对象里面的父类。相当于在这个程序里,我们使用super来指向当前子类对象里面的父类/基类/超类,用this来指向对象自己。
//示例 class FatherClass { public int age; public void f() { age = 60; System.out.println("父类的年龄=" + age); } } class ChildClass extends FatherClass { //子类除了继承父类所具有的age属性外,又声明一个,此时的子类拥有两个age属性。 public int age; //在子类ChildClass里面重写了由父类继承下来的f()方法的方法体。 public void f() { super.f(); //这里的super作为父类对象的引用对象用来调用父类对象里面的f()方法 //打印输出语句 age = 20;//这个value是子类自己定义的age,不是从父类继承下来的那个age System.out.println("子类的年龄=" + age); //打印出来的是子类自定义的age值,值为20。 System.out.println(age); System.out.println(super.age); } } 复制代码
//测试类 public class Test { public static void main(String[] args) { ChildClass cc = new ChildClass(); cc.f(); } } /* 父类的年龄=60 子类的年龄=20 20 60 */复制代码
封装可以隐藏对象的内部实现,就是将对象的属性和方法封装成一个独立的整体,调用的时候只要知道如何调用即可,而且内部的改进不会影响外部调用。就好像我们使用遥控器,只需要知道怎么操作就行了,没有必要了遥控器的内部结构。
Java中使用访问控制符来控制哪些细节需要封装,哪些细节需要暴露。一共有四种访问控制,分别是 private、default、protected、public ,我们需要利用他们尽可能让访问权降到最低,从而提高安全性。
private: 表示私有的,是最严格的访问级别,只有自己类可以访问,使用对象是 变量与方法 。private修饰的变量只能通过类中公共的get方法被外部访问。
public class Person { private int age; public int getAge() { //通过get方法来访问private修饰的变量。 return this.age; } public void setAge(int age) { this.age = age; //通过set方法来修改private修饰的变量。 } }复制代码
default: 表示默认,即什么都不写不使用任何修饰符,只有同一个包的类能访问,使用对象是 类、接口、变量与方法 。
//示例 int i = 1; //没有任何修饰符修饰int。 boolean isTall() { //没有任何修饰符修饰方法boolean。 return true; }复制代码
protected: 表示可以被同一个包的类以及以及其他包中的所有子类访问,这样的话就保护了不相关的类使用这些方法和变量了。使用对象是 变量与方法 。接口及接口的成员变量和方法不能声明为protected。
//示例 class Person { protected boolean isTall() { return true; } } class Player extends Person { protected boolean isTall() { //此处省略...重写父类的isTall()方法。 } } //如果把isTall()方法声明为private,那么除了Person之外的类将不能访问。 //如果把isTall()方法声明为public,那么所有的类都可以访问该方法。 //如果只想让isTall()方法对其所在类的子类可见,则将该方法声明为protected。复制代码
public: 表示可以被该项目的所有包中的所有类所访问,使用对象是 类、接口、变量与方法 。
//示例 //Java程序的main()方法必须设置成public,否则Java解释器将不能运行该类。 public static void main(String[] args) { //代码... }复制代码
父类中声明的public的方法在子类中也必须为public。
父类中声明的protected的方法在子类中要么声明为protected,要么声明为public,不能为private。
父类中声明的private的方法不能被继承。
static 修饰符
静态变量:用static 关键字来声明的成员变量为静态变量,也称为类变量,它为该类的公共变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝,因此通过静态引用修改其属性值,结果是所有对象中的属性值都被修改。 要注意的是局部变量不能声明为static变量 。
静态方法:用static声明的方法为静态方法,可以通过类名称直接访问,非静态方法可以调用static声明的属性或者方法,而 static声明的方法不能调用非static声明的属性或者方法 ,因为非静态变量在通过new创建对象而初始化的,而static类型的方法在对象还没有创建的时候就可以被类名调用,因此在对象还没创建的情况下,如果使用static静态方法调用了非静态方法的属性或者方法,在逻辑顺序上就说不通了,所以在static方法中不可以访问非static的成员。
final修饰符
使用final声明的类不能被继承 ,没有类能够继承final的任何特征。使用final声明的方法内容不能被重写 override (父类和子类有同样的方法名和参数),但是可以被重载 overload (方法名相同,参数不同,最常用的即是构造方法的重载)。 使用final声明的变量即是常量不可以进行修改 ,使用final声明变量时,字母需要全部大写。如果一个程序中的变量使用public static final声明,则该变量将成为全局常量。
图:不能重写final修饰的方法
图:不能重写final修饰的方法
abstract修饰符
抽象方法:使用abstract修饰的方法, 没有方法体,只有声明 。定义的是一种 “规范” ,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类: 包含抽象方法的类就是抽象类 。通过abstract方法定义规范,要求任何继承抽象类的子类必须实现父类所有抽象的方法(除非该子类也是抽象类)。抽象方法的声明以分号结尾,例如:abstract void study();
//示例 abstract class Person { abstract public void study(); //抽象方法 } class Student extends Person { //子类必须实现父类的抽象方法,否则编译出错。 public void study() { System.out.println("每天进步一点点!"); } } //测试抽象类 public class Test{ public static void main(String[] args) { Student s1 = new Student(); s1.study(); } }复制代码
抽象类只能被继承。
抽象方法是需要声明不需要实现。
包含一个抽象方法的类只能定义成抽象类。
抽象类中的抽象方法不要使用private声明。
抽象类和抽象方法都要使用abstract关键字声明。
抽象类不能实例化,即不能用new来实例化抽象类。
抽象方法必须被子类实现,子类只要不是抽象类,必须重写抽象类中的全部抽象方法。
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。 比如:同样是调用人的“锻炼”方法,孙杨是游泳,姚明是篮球,马龙是打乒乓球。事物在运行过程中可能存在不一样的行为。
多态存在的三个必要条件:
1.子类继承父类
2.子类重写父类方法
3.父类引用指向子类对象,比如Parent p = new child();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,说明是子类新定义的方法,此时编译会出错;如果有,再去调用子类的同名方法,而不是父类的实现,这就是多态。
//父类 public class Athlete { int age = 10; public void exercise() { System.out.println("运动员锻炼"); } } //子类 class XunYang extends Athlete { int age = 20; public void exercise() { System.out.println("孙杨游泳锻炼"); } public void run() { System.out.println("孙杨正在跑步"); } } //子类觉得父类方法不够好,重写一个! class YaoMing extends Athlete { int age = 30; public void excercise() { System.out.println("姚明打篮球锻炼"); } } class MaLong extends Athlete { int age = 40; public void exercise() { System.out.println("马龙打乒乓球锻炼"); } }复制代码
//测试类1 public class Test { public static void main(String[] args) { Athlete a = new XunYang(); a.excercise(); // a.run(); 编译报错,需要向下强制转型 System.out.println("年龄是" + a.age); } } /* 运行结果:孙杨游泳锻炼 年龄是10 */ 复制代码
当满Java多态的三个条件时,可以发现a.exercise()调用的实际上是子类的exercise方法,但a.age调用的还是父类的age,而a.run()则不会通过编译。
//测试类2 public class Test { public static void main(String[] args) { Athlete a1 = new XunYang(); //向上自动转型 SportsExercise(a1); Athlete a2 = new YaoMing(); SportsExercise(a2); Athlete a3 = new MaLong(); SportsExercise(a3); //由于这里调用的是自己新的方法,是父类没有的方法。 //a1.run();编译报错,这里必须要强制向下转型。 XunYang ath = (XunYang) a1; ath.run(); System.out.println(("年龄是" + a1.age)); } //这里就可以看做是同一个方法“锻炼”,产生多态。 //如果没有多态,我们这里需要写很多重载的方法 //每增加一个运动员,就需要重载一个锻炼的方法,非常麻烦 /* static void Athlete(XunYang x) { x.exercise; } static void Athlete(YaoMing y) { y.exercise; } ... */ static void SportsExercise(Athlete a) { a.exercise(); } } /* 运行结果:孙杨游泳锻炼 姚明打篮球锻炼 马龙打乒乓球锻炼 孙杨正在跑步 年龄是10 */复制代码
上面是多态最为多见的一种用法,用父类当做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。