面向对象编程有三大特征:封装、继承和多态
一.封装
思维导图概览:
1. 封装的概念
—— 把抽象出的数据(属性)和对数据的操作(方法)封装到一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(方法),才能对数据进行操作
2. 封装的理解和优势
1)隐藏实现细节:调用方法时才能传入参数,不能直接进行对数据的改动
2)可以对数据进行验证,保证安全合理
3. 封装的实现
1)将属性进行私有化(private)【不能直接修改属性】
2)提供公共的(public)set/get方法,对数据进行判断并赋值
a. 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
b. 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
3)提供相应的构造器/构造方法,与set/get方法结合
4.案例分析
class Student {
//成员变量
private String name;
private int age;
//构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//对数据进行校验
if(age >= 0 && age <= 150) {
this.age = age;
} else {
this.age = 0;
}
}
public int getAge() {
return age;
}
public void show() {
System.out.println(name + "," + age);
}
}
/*
创建对象并为其成员变量赋值的两种方式
1:无参构造方法创建对象后使用setXxx()赋值
2:使用带参构造方法直接创建带有属性值的对象
*/
public class StudentDemo {
public static void main(String[] args) {
//无参构造方法创建对象后使用setXxx()赋值
Student s1 = new Student();
s1.setName("林青霞");
s1.setAge(30);
s1.show();
//使用带参构造方法直接创建带有属性值的对象
Student s2 = new Student("林青霞",30);
s2.show();
}
}
二.继承
思维导图概览:
1. 为什么需要继承?
—— 如果两个类有很多相同的属性和方法,这个时候就可以使用继承来提高代码的复用性
2. 继承基本了解和继承示意图
1)继承的了解:继承可以解决代码复用,当多个类中存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
2)继承的格式
class Dog extends Animal { }
3)继承示意图
3. 继承的基本语法
1)class 子类 extends 父类 {}
2)子类就会自动拥有父类定义的属性和方法
3)父类又叫做超类,基类
4)子类又叫派生类
4. 一个小case快递了解继承
public class Student {
public String name;
public int age;
private double score;
public void setScore(double score) {
this.score = score;
}
public double getScore() {
return score;
}
public void show() {
System.out.println("学生名:" + name + " 年龄:" + age + " 成绩:" + score);
}
}
public class Pupil extends Student {
public void testing() {
System.out.println("小学生 " + name + "age = " + age + " 正在考小学数学");
}
}
public class Graduate extends Student {
public void testing() {
System.out.println("大学生" + name + " 年龄为:" + age + " 正在考大学数学");
}
public void print() {
System.out.println("大学生的分数为 " + getScore());
}
}
public class Test {
public static void main(String[] args) {
Pupil pupil = new Pupil();
Graduate graduate = new Graduate();
pupil.name = "小米";
pupil.age = 10;
pupil.testing();
graduate.name = "小强";
graduate.age = 20;
graduate.setScore(80.0);
graduate.testing();
graduate.print();
}
}
5. 继承的优势与劣势
1)优势
a. 代码的复用性提高了
b. 代码的扩展性和维护性提高
2)劣势
—— 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
6. 继承的细节讨论及实现
1)子类继承了所有的属性和方法,非私有的属性和方法可以在子类中直接访问,但是私有属性和方法的访问,要通过父类提供公共的方法去访问
public class Test1 {
public static void main(String[] args) {
//给A中的私有属性进行赋值
A a = new A();
a.setA(18);
a.setAge(20);
//B继承A后,可访问
B b = new B();
b.getMethod();
System.out.print(b.getA());
System.out.print(b.getAge());
}
}
class A {
public int age;
private int a;
//无参构造器
public A() {
}
//有参构造器
public A(int age, int a) {
this.age = age;
this.a = a;
}
//相关的set/get方法 --> 用来访问私有属性
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
//私有方法
private void method() {
System.out.println("这是父类的私有方法~~~");
}
//通过公共方法去访问私有方法
public void getMethod() {
method();
}
}
class B extends A {}
2)子类必须调用父类的构造器,完成父类的初始化(子类中所有的构造方法默认都会访问父类中无参的构造方法)
3)当创建子类对象时,不管使用子类的那个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的那个构造器完成对父类的初始化工作,否则,编译不会通过
4)想要指定去调用父类的某个构造器,则可以显示的调用一下:super(参数列表);
5)super在构造器中使用时,必须放在构造器的第一行
public class Test1 {
public static void main(String[] args) {
}
}
class A {
public int age;
private int a;
//无参构造器
public A() {
}
//有参构造器
public A(int age, int a) {
this.age = age;
this.a = a;
}
//相关的set/get方法 --> 用来访问私有属性
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
class B extends A {
//1.若是一样都没有写,则存在一个默认的无参构造器public A() {},默认的无参构造器中存在父类的无参构造器
public B(){
//2. 写出了子类的无参构造器,在无参构造器中默认调用了父类的无参构造器,无法对父类进行初始化,因为父类的无参构造器中也没有属性,注意:super()只能放在第一行
}
public B(int age , int a) {
super(age, a);// 3.显示调用了父类的有参构造器(则不再会调用父类的默认无参构造器),对父类的属性进行初始化,注意:super()只能放在第一行
}
}
6)super() 和 this()都只能放在构造器的第一行,因此这两个关键字不能共存于同一个构造器中
class A () {
}
class B extends A{
public B() {
this(age);//表示到进入到下面的这个有参构造器中去了
//使用this的使用无法使用默认是super();
}
public B(int age) {
//注意:里面默认存在一个super();
}
}
7)Java所有类的父类都是Object类,Object类是所有类的父类(顶级父类)
public class Test1 {
public static void main(String[] args) {
}
}
class A extends Object{
}
class B extends A {
}
8)子类最多只能继承一个父类(指直接父类),即Java是单继承机制
public class Test {
public static void main(String[] args) {
}
}
class A {}
class B {}
class C extends B extends A {}//错误代码,Java是单继承模式
注:不能滥用继承,子类和父类之间必须满足is - a的逻辑关系
意思是如果有一个动物类(父类),你创建了一个猫类,继承动物类, 这是满足 is - a的形式,但是如果你创建了一个树木类,继承了动物类, 则不满足is - a 的形式
7. 继承的本质分析
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//?-> 这时请大家注意,要按照查找关系来返回信息
//(1) 首先看子类是否有该属性
//(2) 如果子类有这个属性,并且可以访问,则返回信息
//(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
//(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object...
System.out.println(son.name);//返回就是大头儿子
System.out.println(son.age);//返回的就是 39
System.out.println(son.getAge());//返回的就是 39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa {//爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {//父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子类
String name = "大头儿子";
}
三.super关键字
1. super介绍
—— super代表父类的引用,用于访问父类的属性、方法、构造器
2. super语法
1)访问父类的属性,但不能访问父类的private属性 ---> super.属性名
2)访问父类的方法,但不能访问父类的private方法 ----> super.方法名(参数列表)
3)访问父类的构造器 ----> super(参数列表);只能放在构造器的第一句,也只能出现一句
3. 一个小case快速了解super
public class Test1 {
public static void main(String[] args) {
}
}
class A {
public int num1;//公共
protected int num2;//受保护
int num3;//默认
private int num4;//私有
//构造器
public A() {
}
public A(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
public A(int num1, int num2, int num3, int num4) {
this.num1 = num1;
this.num2 = num2;
this.num3 = num3;
this.num4 = num4;
}
//set/get方法
public int getNum1() {
return num1;
}
public void setNum1(int num1) {
this.num1 = num1;
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
public int getNum3() {
return num3;
}
public void setNum3(int num3) {
this.num3 = num3;
}
public int getNum4() {
return num4;
}
public void setNum4(int num4) {
this.num4 = num4;
}
//私有方法
private void print() {
System.out.println("这是A类中的私有方法");
}
}
class B extends A {
public B(int num1, int num2 ,int num3 ,int num4) {
//super();//访问父类的无参构造器,不写也默认有
//super(num1,num2); 访问父类中的有有两个参数的构造器,使用这个构造器时,默认构造器不执行
super(num1, num2, num3, num4);
}
public void pout() {
super.num1 = 10;
super.num2 = 20;
super.num3 = 30;
//super.num4 = 40; 错误,私有成员变量不能访问
//super.print(); 错误,私有方法不能访问
}
}
4. super的细节讨论及实现
1)调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化)
2)当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super和this直接访问是一样的效果
3)super的访问不限于直接父类,如果爷爷类和本类中有相同的成员,也可以使用super去访问爷爷类中的成员;如果有多个基类(上级类)中都有同名的成员,使用super访问时遵循就近原则。
5. this和super的比较
区别点 | this | super |
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 从父类开始查找 |
调用方法 | 访问本类中的方法,如果本类中没有此方法,则从父类中继续查找 | 从父类中开始查找 |
调用构造器 | 调用本类的构造器,必须放在构造器首行 | 调用父类的构造器,必须放在子类的首行 |
特殊 | 表示当前对象 | 子类中访问父类的对象 |
四.方法重写
1. 方法重写概念
—— 方法重写就是子类有一个方法和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法重写了父类的方法
2. 一个小case快速了解方法重写
public class Test {
public static void main(String[] args) {
B b = new B();
b.print();//?输出什么? ---》 父类中的print()方法被子类中的print()方法重写了~
//注意:重写的权限修饰,父类中的一定不能是private,不然不能重写,且子类重写的方法不能降低父类方法的访问权限,但可以提高
}
}
class A {
private String name;
private int age;
void print() {
System.out.println("name = " + name + " age = " + age);
}
public A() {
}
public A(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class B extends A {
public void print() {
System.out.println("父类中的print()方法被子类中的print()方法重写了~");
}
}
注:重写的权限修饰符,父类方法中的一定不能是private,不然不能重写,且子类重写的方法不能降低父类方法的访问权限,但可以提高【public > protected > 默认 > private】
3. 方法重写的细节讨论及实现
1)子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一样
2)子类方法中的返回类型和父类方法中的返回类型一样,或者是父类返回类型的子类
//如:父类返回类型是 Object,子类方法返回类型是String,String是Object的子类
//父类方法:
public Object getInfo() {}
//子类方法
public String getInfo() {}
3)子类方法不能缩小父类方法的访问权限,但能提高【public > protected > 默认 > private】
4. 方法重写与方法重载的比较
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
方法重载(overload) | 本类 | 必须一样 | 类型,个数或者顺序至少有一个不同 | 无要求 | 无要求 |
方法重写(override) | 父子类 | 必须一样 | 相同 | 子类重写的方法,返回的类型和父类返回的类型一致,或者是父类的子类 | 子类不能缩小父类方法的访问范围 |
五.多态
思维导图概览:
1. 多态的介绍
—— 方法或对象具有多种形态。多态是面向对象的第三大特征,多态是建立在封装和继承的基础上的
2. 多态的具体体现
1)方法的重写和方法的重载就体现多态
2)对象的多态
3. 如何判断编译类型和运行类型
1)编译类型看 = 号的左边,运行类型看 = 号的右边
2)编译类型在定义对象时,就确定了,不能改变
3)运行类型是可以改变的
4)一个对象的编译类型和运行类型可以不一致
//编译类型为animal,运行类型为Tiger
Animal animal = new Tiger();
//编译类型为animal,运行类型为Cat
animal = new Cat();
4. 一个小case快速了解对象的多态
public class TestOne {
public static void main(String[] args) {
//编译类型是Animal, 运行类型是Tiger
Animal tiger = new Tiger();
tiger.print();//打印什么? 老虎会咆哮~~~
//为什么会打印老虎会咆哮? ---> 我们使用多态的时候,先看看子类中里面是否有重写父类的方法,如果有,那就使用,
//如果没有,在父类中查看有没有该方法,如果有则使用
}
}
public class Animal {
public String name;
public int age;
public void print() {
System.out.println("动物会睡觉~~");
}
//构造器
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
//提供成套的set/get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Tiger extends Animal{
public void print() {
System.out.println("老虎会咆哮~~");
}
}
5. 多态的细节讨论及实现
1)多态的向上转型
a. 本质:父类的引用指向子类的对象
//父类
class Animal{}
//子类
class Cat() extends Animal {}
//父类的引用指向了子类的对象
Animal cat = new Cat(); //编译类型:Animal,运行类型:Cat
b. 语法:父类类型 引用名 = new 子类类型();
c. 特点:编译类型看左边,运行类型看右边。可以调用父类中的所有成员变量(需遵守访问权限),不能调用子类中的特有成员,可以调用方法重写,注意,在访问的时候,从运行类型开始访问。
public class TestOne {
public static void main(String[] args) {
//编译类型是Animal, 运行类型是Tiger
Animal tiger = new Tiger();
tiger.print();//打印什么? 老虎会咆哮~~~
//为什么会打印老虎会咆哮?
// ---》 我们使用多态的时候,先判断编译类型中是否有该方法,如果没有,则报错,运行类型从子类开始查找,查找与访问相匹配的方法或属性,如果父类中的方法被重写了,则调用重写的方法
//看看访问特有方法是否报错
//tiger.output(); --> 错误,不能访问子类中特有的方法
}
}
public class Animal {
public String name;
public int age;
public void print() {
System.out.println("动物会睡觉~~");
}
//构造器
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
//提供成套的set/get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Tiger extends Animal {
public void print() {
System.out.println("老虎会咆哮~~~");
}
//特有的方法
public void output() {
System.out.println("老虎在打滚~~");
}
}
2)多态的向下转型(在向上转型的基础上)
a. 语法:子类类型 引用名 = (子类类型) 父类引用;
/*
怎么理解?
1.因为向上转型无法访问子类中的特有方法,但是,向下转型则帮助我们解决了此问题
2.怎么理解 子类类型 引用名 = (子类类型) 父类引用?
//一段代码搞定:
//编译类型:Animal,运行类型:Tiger
Aniaml animal = new Tiger();
//向下转型之后,编译类型Tiger,运行类型:Tiger
Tiger tiger = (Tiger)animal;
//子类类型 引用名 = (子类类型)父类引用
//解决!!!
3.思考以下代码能不能向下转型?
Animal animal = new Cat();
Dog dog = (Dog)animal;
//错误,为什么?因为我们没有使用父类的引用类型指向子类的对象,前面说过了,向下转型是基于向上转型的,所以想要向下转型的前提是要使父类的引用指向子类对象,也就是向上转型
//代码改正
Animal animal = new Dog();
Dog dog = (Dog)animal;
*/
b. 只能强转父类的引用,不能强转父类的类型
//代码解释
Person p = new Xh();
Xh xh = (Xh)Person;//错误,这样是强转父类的类型
c. 要求父类的引用必须指向的是当前目标类型的对象(看a.的解释)
d. 向下转型后,可以调用子类类型中的所有成员(属性和方法)
public class TestOne {
public static void main(String[] args) {
//编译类型是Animal, 运行类型是Tiger
Animal animal = new Tiger();
animal.print();//打印什么? 老虎会咆哮~~~
//看看访问特有方法是否报错
//tiger.run(); --> 错误,不能访问子类中特有的方法
//对父类的引用进行强转
Tiger tiger = (Tiger)animal;
tiger.run();//可以使用特有方法了
}
}
public class Animal {
public String name;
public int age;
public void print() {
System.out.println("动物会睡觉~~");
}
//构造器
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
//提供成套的set/get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Tiger extends Animal{
//重写的方法
public void print() {
System.out.println("老虎爱睡懒觉~~~");
}
//特有方法
public void run() {
System.out.println("老虎跑得快~~~");
}
}
3)属性没有重写的说法,属性的值看编译类型
public class Test {
public static void main(String[] args) {
Person person = new Smith("jack",18,"smith",20);
Smith smith = new Smith();
Person p = new Person();
System.out.println(person.name); //jack
System.out.println(person.age);//18
System.out.println(smith.name);//null ? --> 因为Smith是我们新开的对象,所有里面的值是默认值
System.out.println(smith.age); //0
Smith s = (Smith)person; //向下转型
System.out.println(s.name); //smith
System.out.println(s.age);20
}
}
public class Person {
public String name;
public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Smith extends Person{
public String name;
public int age;
public Smith() {
}
public Smith(String name, int age, String name1,int age1) {
super(name, age);
this.name = name1;
this.age = age1;
}
}
4)instanceof 比较操作符,用于判断对象的运行类型是否是XX类型或XX类型的子类型
public class Test {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);//true
System.out.println(bb instanceof AA);//true
//编译类型:AA 运行类型:BB
AA aa = new BB();
System.out.println(aa instanceof AA); //true
System.out.println(aa instanceof BB); //true
Object obj = new Object();
System.out.println(obj instanceof AA); //false
String str = "hello";
//System.out.println(str instanceof AA); //编译器报错,因为两者是不相同的类型
System.out.println(str instanceof Object); //true
}
}
class AA {}
class BB extends AA {}
六.Java的动态绑定机制
1. Java重要特性:动态绑定机制
2. Java动态绑定机制的特点
1)当调用对象方法的时候,该方法会和对象的内存地址/运行类型进行绑定
2)当调用对象属性的时,没有动态绑定机制,哪里声明,哪里使用
public class Test {
public static void main(String[] args) {
AA aa = new BB();
System.out.println(aa.sum()); //40
System.out.println(aa.sum1()); //30
//为什么会是以上答案?因为运行类型从子类开始执行,查找当中的方法,执行了父类中发方法重写
//去掉子类中的sum()方法
System.out.println(aa.sum()); //30 --> Java的动态绑定机制:父类中的sum()方法调用子类中重写的getI()方法
}
}
class AA {
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class BB extends AA {
public int i = 20;
//注意:以下方法全部为重写方法
/* public int sum() {
return i + 20;
}*/
public int getI() {
return i;
}
public int sum1() {
return i + 10;
}
}
3.多态数组
—— 数组的定义类型为父类类型,里面保存的对象类型为子类类型或父类类型
public class Test {
public static void main(String[] args) {
Person[] person = new Person[2];
person[0] = new Person("jack",19);
person[1] = new Student("小红",10,100);
//调用say()方法
for (int i = 0; i < person.length; i++) {
System.out.println(person[i].say());
//调用子类的特有方法
if(person[i] instanceof Student) {
//向下转型,调用子类中特有的方法
((Student) person[i]).study();
}
}
}
}
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String say() {//返回名字和年龄
return name + "\t" + age;
}
}
public class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
//重写父类 say
@Override
public String say() {
return "学生 " + super.say() + " score=" + score;
}
//特有的方法
public void study() {
System.out.println("学生 " + getName() + " 正在学 java...");
}
}
2. 多态参数
—— 方法定义的形参类型为父类类型,实参类型为父类类型或子类类型
public class Test {
public static void main(String[] args) {
Test test = new Test();
Person[] person = new Person[2];
person[0] = new Person("jack",19);
person[1] = new Student("小红",10,100);
test.printSay(person[0]);
test.printSay(person[1]);
}
public void printSay(Person p) {
System.out.println(p.say());
}
}
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String say() {//返回名字和年龄
return name + "\t" + age;
}
}
public class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
//重写父类 say
@Override
public String say() {
return "学生 " + super.say() + " score=" + score;
}
}
七.问题思考?
1. 一个类可以有几个直接父类?一个父类可有多少个子类?子类能获取直接父类的父类中的结构吗?子类能否获取父类中private权限的属性或方法?
2. 方法的重写(override/overwrite)的具体规则有哪些,两者的区别?
3. super调用构造器,有哪些具体的注意点?
4. 如何实现向下转型?需要注意什么问题?如何解决此问题?
5. == 和equals()有何区别?