面向对象编程三大特征
1. 封装
1.1 封装介绍
封装就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
1.2 封装的理解和好处
隐藏实现细节:方法(链接数据库)<–调用(传入参数…)
可以对数据进行验证,保证安全合理
1.3 封装的实现步骤
- 将属性进行私有化private(不能直接修改属性)
- 提供一个公共的(public)set方法,用于对属性判断并赋值。
- 提供一个公共的(public)get方法,用于获取属性的值。
1.4 案例演示
实现员工类,不能随便查看人的年龄,工资等隐私,并对其年龄进行合理的验证,年龄合理就设置,否则给默认年龄30岁,年龄要求必须在20~50岁之间。
public class Employee {
private String name;
private int age;
private int salary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age>=20&&age<=50){
this.age = age;
}else {
this.age = 20;
}
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public Employee(String name, int age, int salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
}
2. 继承
2.1 为什么需要继承
一个小问题,我们需要编写两个类,一个是小学生(Pupil),一个是学生(Student),两个类的属性和方法中有许多是相同的,我们是否可以将他们的共同点利用起来–> 继承(代码复用性)
2.2 继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中 抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来 声明继承父类即可。
2.3 继承的基本语法
class 子类e ntends 父类{
}
子类就会自动拥有父类定义的属性和方法
父类又叫基类,超类。
子类又叫派生类。
2.4 继承给编程带来的便利
- 代码复用性大大提高
- 代码的拓展性和维护性提高了
2.5 继承相关细节问题
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
- 子类必须调用父类的构造器, 完成父类的初始化 。
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
- 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表) 。
- super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)。
- super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器。
- Java 所有类都是 Object 类的子类, Object 是所有类的基类.。
- 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)。
- 子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制。
这里关于第3点我们详细介绍
public class A {
private String name;
private int age;
public A() {
System.out.println("父类无参构造器被调用");
}
public A(String name, int age) {
this.name = name;
this.age = age;
System.out.println("父类有参构造器被调用");
}
public void test(){
System.out.println("我是父类的test方法");
}
}
public class B extends A{
private String name;
private int age;
private int salary;
public B() {
super();
System.out.println("子类无参构造器调用");
}
public B(String name, int age, int salary) {
super(name, age);
System.out.println("子类有参构造器调用");
this.name = name;
this.age = age;
this.salary = salary;
}
}
public static void main(String[] args) {
B b = new B();
B b1 = new B("小白", 11, 8000);
}
2.6 继承的内存布局
3. 多态
3.1 为什么需要多态
请编写一个Master类,喂养Cat和Dog,对于不同的Cat和Dog要喂不同的食物
对于这种问题,多态的重要性就得到了极好的体现,如果不使用多态,代码无疑会及其冗长。下面,我们详细讲解
3.2 多态基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
3.2 多态的具体体现
- 方法的多态
重写和重载就体现多态 [案例说明]
public class PloyMethod
{
public static void main(String[] args) {
//方法重载体现多态
A a = new A();
//这里我们传入不同的参数,就会调用不同 sum 方法,就体现多态
System.out.println(a.sum(10, 20));
System.out.println(a.sum(10, 20, 30));
//方法重写体现多态
B b = new B(); a.say(); b.say();
}
}
class B {
//父类
public void say() {
System.out.println("B say() 方法被调用...");
}
}
class A extends B {
//子类
public int sum(int n1, int n2){
//和下面 sum 构成重载
return n1 + n2;
}
public int sum(int n1, int n2, int n3){
return n1 + n2 + n3;
}
- 对象的多态
1.一个对象的编译类型和运行类型可以不一致
2.编译类型在定义对象时,就确定了,不能改变
3.运行类型是可以变化的
4.编译类型看定义时 = 号的左边, 运行类型看 = 号的右边
public class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
public class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
public class Dog extends Animal {
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
public class PolyObject
{
public static void main(String[] args) {
//体验对象多态特点
//animal 编译类型就是 Animal , 运行类型 Dog
Animal animal = new Dog();
//因为运行时 , 执行到改行时,animal 运行类型是 Dog,所以 cry 就是 Dog 的 cry
animal.cry(); //小狗汪汪叫
//animal 编译类型 Animal,运行类型就是 Cat
animal = new Cat();
animal.cry(); //小猫喵喵叫
}
}
3.3 多态快速入门案例
public class Food {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Food(String name) {
this.name = name;
}
}
class Bone extends Food{
private String name;
Bone(String name) {
super(name);
this.name = name;
}
}
class Fish extends Food{
private String name;
Fish(String name) {
super(name);
this.name = name;
}
}
public class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Animal(String name) {
this.name = name;
}
}
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
public class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
public class Master {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Master(String name) {
this.name = name;
}
//使用多态机制,可以统一的管理主人喂食的问题
//animal 编译类型是 Animal,可以指向(接收) Animal 子类的对象
//food 编译类型是 Food ,可以指向(接收) Food 子类的对象
public void feed(Animal animal, Food food) {
System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
}
}
public static void main(String[] args) {
Animal animal = new Cat("小白");
Food food = new Fish("鱼")
Master master = new Master("小明");
master.feed(animal,food);
animal = new Dog("小花");
food = new Bone("骨头");
master.feed(animal,food);
}
3.4 多态注意事项和细节
- 多态的前提是:两个类之间存在继承关系。
- 多态的向上转型:
本质: 父类的引用指向了子类的对象
语法:父类类型 引用名 = new 子类类型();
特点:1. 编译类型看左边,运行类型看右边。
2.可以调用父类中的所有成员(需遵守访问权限);
3.不能调用子类中的特有成员;
4.最终运行结果看子类的具体实现。
- 多态向下转型:
语法: 子类类型 引用名 = (子类类型)父类引用;
只能强转父类的引用,不能强转父类的对象;
要求父类的引用必须指向的是当前目标类型的对象;
当向下转型后,可以调用子类类型中所有的成员。
3.5 Java动态绑定机制(非常重要)
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
class A{
public int i = 10;
public int sum(){
return getI()+10;
}
public int sum1(){
return i+10;
}
public int getI(){
return i;
}
}
class B extends A{
public int i = 20;
public int sum(){
return i+20;
}
public int sum1(){
return i+10;
}
public int getI(){
return i;
}
}
//mian方法中
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//40
System.out.println(a.sum1());//30
}
如果把子类B中的sum方法注释掉,那么执行a.sum()时就会根据继承找父类的sum方法,父类的sum方法中存在getI()这一方法,这时,Java的动态绑定机制便体现出了,当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定*,因此运行类型是B,会获取B类中的i值,也就是20,然后返回给父类中的sum方法,答案是30;
如果把子类B中的sum1方法注释掉,那么和之前一样会去找父类的sum1方法,但这时,父类的sum1方法是直接返回i+10的值,此时这个i,根据当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用,就是父类中的i,也就是值为10,因此此时返回值为20;