面向对象的 3 个核心特性:继承、封装和多态;
封装
封装(encapsulation):把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部门只能通过被授权的操作[方法],才能对数据进行操作。、
Java 语言的基本封装单位是类;
作用
保护类中的信息;
- 它可以阻止在外部定义的代码随意访问内部代码和数据。
隐藏实现细节,仅暴露使用的方式;
有助于建立各个系统之间的松耦合关系,提高系统的独立性
- 当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。
提高软件的复用率,降低成本
- 每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。
- 例如,一个 U 盘可以在多台电脑上使用。
实现
1、在属性的前面加private修饰
- 不能直接修改属性
2、提供一个公共(public)的赋值(setter)方法,用于对属性判断并赋值
public void setXxx(类型 参数名){ //Xxx表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
- 可以对属性进行限制操作,从而给类中的属性赋予合理的值
3、提供一个公共(public)的取值(getter)方法,用于获取属性的值
public 数据类型 getXxx(){ //权限判断;Xxx表示某个属性
return xx;
}
- 获取类中属性的值(也可以直接调用类中的属性名称来获取属性值)
4、在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断)
public class Encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("韩工");
person.setAge(30);
person.setSalary(30000);
System.out.println(person.info());
System.out.println(person.getSalary());//需要通过getSalary()方法访问人的工资
System.out.println(person.name);//可以直接访问人的名字;
// 如果我们自己使用构造器指定属性
Person smith = new Person("smith", 80, 50000);
System.out.println("====smith的信息======");
System.out.println(smith.info());
}
}
/*
需求:不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。
年龄合理就设置,否则给默认年龄, 必须在 1-120;
name的长度在 2-6字符 之间;
*/
class Person {
public String name; // 名字公开
private int age; // age 私有化
private double salary; //工资 私有化
public void say(int n, String name) {
}
// 构造器 alt+insert
public Person() {
}
// 有三个属性的构造器
public Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
// 使用快捷键alt+insert 写setXxx 和 getXxx,然后根据要求来完善我们的代码.
public String getName() {
return name;
}
public void setName(String name) {
// 加入对数据的校验,相当于增加了业务逻辑
if (name.length() >= 2 && name.length() <= 6) {
this.name = name;
} else {
System.out.println("名字的长度不对,需要(2-6)个字符,默认名字");
this.name = "无名人";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 判断
if (age >= 1 && age <= 120) {// 如果是合理范围
this.age = age;
} else {
System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄18 ");
this.age = 18;// 给一个默认年龄
}
}
public double getSalary() {
// 可以这里增加对当前对象的权限判断
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
// 写一个方法,返回属性信息
public String info() {
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
继承
继承(extend):程序中的继承性是指子类拥有父类的全部特征和行为,这是类之间的一种关系;
- 当多个类之间具有共同的特征,将共同的部分抽取成一个父类,通过继承这个父类,而达到“代码复用”;
- 模拟,表现现实世界中一个“is-a”的逻辑关系;
- 继承设计的基本思想,父类的构造器完成父类属性初始化,子类的构造器完成子类属性初始化;
//语法:使用extends声明继承父类
class 子类名 extends 父类名{
}
说明
1、子类会继承父类的所有的属性和方法,但是不会继承父类的构造器;
- 子类也会继承父类的私有的属性与方法,但是在子类中不能直接使用父类的私有成员,通过父类提供公共的方法去访问;
- 子类继承了所有的属性和方法, 非私有的属性和方法可以在子类直接访问;
2、子类一定会调用父类的构造器;
- 如果子类构造器中,没有写super()或super(实参列表)语句,那么默认就是调用父类“无参构造”
- 子类的构造器中,也可以指定调用父类的构造器: super()或super(实参列表)
- 当父类没有无参构造时,子类的构造中必须手动调用父类的有参构造器: super(实参列表)
- super()和 this()都必须放在构造器第一行,因此这两个方法不能共存在一个构造器;[super只能在构造器中使用]
- 父类构造器的调用不限于一个父类(指直接继承),将一直往上追溯到Object类(顶级父类)
//继承关系分析
/**
* 继承关系:Sub extends Base ; Base extends TopBase;
*/
public class ExtendsTest01 {
public static void main(String[] args) {
// 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
Base base = new Base();
System.out.println("================================");
// 构造器 TopBase() 被调用...
// 父类 Base() 无参构造器被调用....
Base base02 = new Base("jack", 20);
System.out.println("================================");
// 构造器 TopBase() 被调用...
// 父类 Base(String name, int age) 有参构造器01被调用....
Base base03 = new Base("tom");
System.out.println("================================");
// 构造器 TopBase() 被调用...
// 父类 Base(String name)有参构造器02被调用....
Sub sub = new Sub();
// sub.sayOk();
System.out.println("================================");
// 构造器 TopBase() 被调用...
// 父类 Base(String name, int age) 有参构造器01被调用....
// 子类 Sub()构造器被调用....
// Sub sub02 = new Sub("jack");
// System.out.println("================================");
Sub sub03 = new Sub("king", 10);
// 构造器 TopBase() 被调用...
// 父类 Base(String name, int age) 有参构造器01被调用....
// 子类 Sub(String name, int age)构造器被调用....
}
}
class Base extends TopBase { // 父类
// 4 个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400; // 私有变量
public Base() { // 无参构造器
System.out.println("父类 Base() 无参构造器被调用....");
}
public Base(String name, int age) {// 有参构造器
// 默认 super()
System.out.println("父类 Base(String name, int age) 有参构造器01被调用....");
}
public Base(String name) {// 有参构造器
System.out.println("父类 Base(String name)有参构造器02被调用....");
}
// 父类提供一个 public 的方法,返回了 n4
public int getN4() {
return n4;
}
public void test100() {
System.out.println("test100");
}
protected void test200() {
System.out.println("test200");
}
void test300() {
System.out.println("test300");
}
private void test400() {
System.out.println("test400");
}
// call
public void callTest400() {
test400();
}
}
// 输入 ctrl + H 可以看到类的继承关系
class Sub extends Base { // 子类
public Sub(String name, int age) {
// 1. 调用父类的无参构造器, 如下或者 什么都不写,默认就是调用 super()
// super();//父类的无参构造器
// 2. 调用父类的 Base(String name) 构造器
// super("hsp");
// 3. 调用父类的 Base(String name, int age) 构造器
super("king", 20);
System.out.println("子类 Sub(String name, int age)构造器被调用....");
}
public Sub() {// 无参构造器
// super(); //默认调用父类的无参构造器,因此父类有无参构造器才能创建子类的无参构造器;
super("smith", 10);
System.out.println("子类 Sub()构造器被调用....");
}
public Sub(String name) {
super("tom", 30);
// do nothing...
System.out.println("子类 Sub(String name)构造器被调用....");
}
public void sayOk() {// 子类方法
// 非私有的属性和方法可以在子类直接访问
// 但是私有属性和方法不能在子类直接访问
System.out.println(n1 + " " + n2 + " " + n3);
test100();
test200();
test300();
// test400();错误
// 要通过父类提供公共的方法去访问
System.out.println("n4=" + getN4());
callTest400();//
}
}
class TopBase { // 父类是 Object
private int id;
public TopBase() {
// super(); Object 的无参构造器
System.out.println("构造器 TopBase() 被调用...");
}
}
3、Java中只支持单继承,但是又支持多层继承;
- Java类只有一个直接父类【单继承机制】;
- Java类又支持代代相传;
- Java类的根父类是java.lang.Object;
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
4、 子类必须调用父类的构造器,完成父类的初始化 ;
//主程序
public class PCTest01 {
public static void main(String[] args) {
PC pc = new PC("I7", 20, 50, "华为");
pc.printInfo(); // PC信息=
// cpu=I7 memory=20 disk=50 brand=华为
}
}
//编写PC子类,继承Computer类,添加特有属性【品牌brand】
public class PC extends Computer{
private String brand;
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
//这里IDEA 根据继承的规则,自动把构造器的调用写好
this.brand = brand;
//这里也体现: 继承设计的基本思想,父类的构造器完成父类属性初始化,子类的构造器完成子类属性初始化
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void printInfo() {
System.out.println("PC信息=");
//调用父类的getDetails方法,得到相关属性信息..
System.out.println(getDetails() + " brand=" + brand);
}
}
//编写Computer父类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
public class Computer {
private String cpu;
private int memory;
private int disk;
public Computer(String cpu, int memory, int disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
//返回Computer信息
public String getDetails() {
return "cpu=" + cpu + " memory=" + memory + " disk=" + disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
}
分析
当子类对象创建好后,建立查找关系:
- (1)首先看子类是否有该属性
- (2)如果子类有这个属性,并且可以访问,则返回信息
- (3)如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
- (4)如果父类没有就按照(3)的规则,继续找上级父类,直到Object...
应用案例
//类的加载顺序: Object --> B --> A
public class ExtendsExercise01 {
public static void main(String[] args) {
B b = new B();//⑦
}
}
class A {
A() {//③
System.out.println("a");//④
}
A(String name) {
System.out.println("a name");
}
}
class B extends A {
B() {
this("abc");//①
System.out.println("b");//⑥
}
B(String name) {//②
// 默认有 super();
System.out.println("b name");//⑤
}
}
//类的加载顺序: Object --> C --> B -->A
public class ExtendsExercise02 {
public static void main(String[] args) {
C c = new C(); // 我是A类 hahah我是B类的有参构造 我是c类的有参构造 我是c类的无参构造
}
}
class A {// A类
public A() {//④
System.out.println("我是A类"); //⑤
}
}
class B extends A { // B类,继承A类
public B() {
System.out.println("我是B类的无参构造");
} //
public B(String name) {//③
System.out.println(name + "我是B类的有参构造");//⑥
}
}
class C extends B { // C类,继承 B类
public C() {
this("hello");//①
System.out.println("我是c类的无参构造");//⑧
}
public C(String name) {
super("hahah");//②
System.out.println("我是c类的有参构造"); //⑦
}
}
多态
面向对象的多态性,即“一个接口,多个方法”;
多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式;
多态性允许一个接口被多个同类使用,弥补了单继承的不足;
Java 实现多态有 3 个必要条件:继承、重写和向上转型;
动态绑定机制
- 在调用对象方法时候,该方法会和该对象的内存地址/运行类型绑定;
- 当调用对象属性时,没有动态调用机制,哪里声明,哪里使用;
作用
- 属于运行时行为
- 代码更灵活
应用
- 1、多态参数: 方法定义的形参类型为父类类型,实参类型允许为子类类型
- 2、多态数组: 数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
- 3、多态属性
//方法的重载和重写
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;
}
public void say() {
System.out.println("A say() 方法被调用...");
}
}
数据类型的转换
案例1:
public class DynamicBinding {
public static void main(String[] args) {
// a 的编译类型 A, 运行类型 B
A a = new B();// 向上转型
System.out.println(a.sum());//?40 -> 30
System.out.println(a.sum1());//?30-> 20
}
}
class A {// 父类
public int i = 10;
// 动态绑定机制:
public int sum() {// 父类 sum()
return getI() + 10;// 20 + 10
}
public int sum1() {// 父类 sum1()
return i + 10;// 10 + 10
}
public int getI() {// 父类 getI
return i;
}
}
class B extends A {// 子类
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {// 子类 getI()
return i;
}
// public int sum1() {
// return i + 10;
// }
}
//案例2
/**
* 继承关系:Cat-->Animal;Dog-->Animal;
*/
public class PolyDetail {
public static void main(String[] args) {
// 向上转型: 父类的引用指向了子类的对象
Animal animal = new Cat();
Object obj = new Cat();// Object 也是 Cat 的父类
//向上转型调用方法的规则如下:
//(1)可以调用父类中的所有成员(需遵守访问权限)
//(2)但是不能调用子类的特有的成员,因为在编译阶段,能调用哪些成员,是由编译类型来决定的
//animal.catchMouse();错误
//(3)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则与前面方法调用规则一致。
animal.eat();// 猫吃鱼..
animal.run();// 跑
animal.show();// hello,你好
animal.sleep();// 睡
// 需求:可以调用 Cat 的 catchMouse 方法
// 多态的向下转型
// cat 的编译类型 Cat,运行类型是 Cat
Cat cat = (Cat) animal;
cat.catchMouse();// 猫抓老鼠
//(2)要求父类的引用必须指向的是当前目标类型的对象
// Dog dog = (Dog) animal; // Cat cannot be cast to com.hspedu.duotai.Dog
System.out.println("ok~~");
}
}
class Animal {//父类
String name = "动物";
int age = 10;
public void sleep() {
System.out.println("睡");
}
public void run() {
System.out.println("跑");
}
public void eat() {
System.out.println("吃");
}
public void show() {
System.out.println("hello,你好");
}
}
class Cat extends Animal {//子类
public void eat() {// 方法重写
System.out.println("猫吃鱼");
}
public void catchMouse() {// Cat 特有方法
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {//子类
}