本文主要针对面向对象的三大特征:继承、封装、多态进行详细的总结。
继承
为什么需要继承?
从英文字面意思理解,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运算符
instanceof是双元运算符,中文解释为“...的例子”,左边的操作元是对象,右边是一个类;当左边的对象是右面的类创建的对象时,返回true,否则返回false
![](/qrcode.jpg)
//示例
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。复制代码
继承的总结要点
- Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链变得复杂,系统难以维护。
- Java中类没有继承类,接口有多继承。
- 子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问,比如父类私有的属性和方法。
- 如果没有定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
方法的重载(overload)
方法的重载是指一个类中可以定义多个方法名相同,但参数不同的方法。 调用时,会根据不同的参数自动匹配对应的方法。
重载的方法,实际是完全不同的方法,只是名称相同而已!
构成条件:形参类型、新参个数、新参顺序不同
//示例
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类基本特性
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 */复制代码
super关键字
在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 */复制代码
封装
封装可以隐藏对象的内部实现,就是将对象的属性和方法封装成一个独立的整体,调用的时候只要知道如何调用即可,而且内部的改进不会影响外部调用。就好像我们使用遥控器,只需要知道怎么操作就行了,没有必要了遥控器的内部结构。
为什么要封装?
- 为了提高了安全性,防止该类的代码和数据被外部随机的访问修改而出现问题,比如当我们在程序中private化age属性,并对外提供了get和set方法,当外界使用set方法设置属性值的时候,可以在set方法里面做个if判断,把age值限制在正常值内,以这种方式就不能随意赋值。
- 为了方便重复使用代码,比如封装好的各种方法,可以随意调用,不用每处都重复方法的具体细节,已经封装好了,你只需要会调用即可,减少了代码量,显得简洁。
封装的实现
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 */复制代码
上面是多态最为多见的一种用法,用父类当做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。