面向对象三大特征⭐⭐⭐⭐⭐
1、封装(encapsulation)
(1)介绍
将数据和具体实现封装在一起
用户只需要通过简单的行为就可以操作数据。
(2)封装的理解和好处
① 隐藏实现细节;方法(连接数据库)<–调用(传入参数)
② 可以对数据进行验证,保证安全合理。
(3)封装实现的步骤
① 将属性进行私有化private 【不能直接修改属性】;
② 提供一个公共的(public)set方法,用于对属性判断并赋值;
public void setXxx(类型 参数名){ //Xxx 表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
③ 提供一个公共的(public)get方法,用于获取属性的值;
public 数据类型 getXxx(){ //权限判断,Xxx 某个属性
return xx;
}
便捷方法:
alt + insert 键 弹出框,选择Getter and Setter即可。
(4)案例
public class Encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("HarryPotter");
person.setAge(3000);
person.setSalary(20000);
person.info();
//工资不能直接看:
// System.out.println(person.salary);//报错
System.out.println(person.getSalary());
//用构造器设置值
Person person1 = new Person("smith",9000,15000);
}
}
class Person {
public String name; //name公开
private int age;//age私有化
private double salary; //工资私有化
//构造器
public Person() {
}
public Person(String name, int age, double salary) {
//为了避免构造器破环私有访问属性的隐私,可以将set方法在构造器内调用即可。
setName(name);
setAge(age);
setSalary(salary);
}
//name's Getter and Setter
//快捷键生成alt+Insert-->Getter and Setter
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 = "无名";
}
}
//age's Getter and Setter
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;
}
}
//salary's Getter and Setter
public double getSalary() {
//这里可以增加对当前用户的权限判断
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//信息打印
public void info(){
System.out.println("name:" + name + "\tage:" + age + "\tsalary:" + salary);
}
}
- 为了避免构造器破环私有访问属性的隐私,可以将set方法在构造器内调用即可。
2、继承(extends)
(1)介绍
- 继承可以解决代码复用
- 当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类种定义这些相同的属性和方法。
- 所有的子类不需要重写定义这些属性和方法,只需要通过extends来声明继承父类即可。
(2)继承的基本语法
class 子类 extends 父类{
}
① 子类就会自动拥有父类定义的属性和方法;
② 父类又叫超类,基类;
③ 子类又叫派生类。
(3)案例
ExtendTest.java
public class ExtendTest {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "jack";
pupil.age = 10;
pupil.testing();
pupil.setScore(50);
pupil.showInfo();
Graduate graduate = new Graduate();
graduate.name = "jack";
graduate.age = 10;
graduate.testing();
graduate.setScore(50);
graduate.showInfo();
}
}
Student.java
public class Student {
//共有属性
public String name;
public int age;
private double score;
//共有方法
public void setScore(double score) {
this.score = score;
}
public void showInfo() {
System.out.println("学生名:"+ name + "年龄" + age + "成绩");
}
}
Pupil.java
//这个extends一定不能忘了写!!
public class Pupil extends Student{
public void testing() {
System.out.println("小学生" + name + "正在考小学数学...");
}
}
Graduate.java
//让Graduate继承Student类
public class Graduate extends Student {
public void testing() {
System.out.println("大学生" + name + "在考大学数学...");
}
}
- 输出
小学生jack正在考小学数学...
学生名:jack年龄10成绩
大学生jack在考大学数学...
学生名:jack年龄10成绩
(4)细节
① 子类访问父类的私有属性和方法
- 子类继承了所有的属性和方法,父类的私有属性和方法不能在子类直接访问,要通过父类提供的公共的方法去访问;
//测试
Pupil pupil = new Pupil();
pupil.setScore(80);
pupil.sayOK();
//子类Pupil
public class Pupil extends Student{
public void sayOK(){
System.out.println(name);
//通过父类提供的公共方法访问私有属性
//相当于父类的公共方法在子类中隐藏了,所以可以直接调用里面的公共方法
System.out.println("score=" + getScore());
//通过父类提供的公共方法访问私有方法
callTest100();
}
}
//父类Person
public class Student{
private double score;
public void setScore(double score){
this.score = score;
}
public double getScore(){
return score;
}
private void test100(){
System.out.println("test100");
}
public void callTest100(){ //公共的方法调用本类的test100
test100();
}
}
② 子类必须调用父类的构造器
- 子类必须调用父类的构造器,完成父类的初始化
//测试
public class ExtendTest {
public static void main(String[] args) {
Pupil pupil = new Pupil();
}
}
//子类
public class Pupil extends Student {
public Pupil(){
//这里其实有一个:
//super(); //默认调用父类的无参构造器
System.out.println("子类Pupil()无参构造器被调用");
}
}
//父类
public class Student {
public Student(){
System.out.println("父类Student()无参构造器被调用");
}
}
- 结果
父类Student()无参构造器被调用
子类Pupil()无参构造器被调用
③ 子类用super(形参列表)调用父类构造器
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
- 如果父类没有提供无参构造器,则必须在子类的构造器种用
super(形参列表)
去指定使用父类的哪个构造器=>完成对父类的初始化工作。 - 否则,编译不会通过;
//测试
public class ExtendTest {
public static void main(String[] args) {
//创建子类对象pupil
Pupil pupil = new Pupil();
}
}
//父类
public class Student {
String name;
int age;
private double score;
//name,age,score
public Student(String name, int age, double score){
System.out.println("父类Student(name,age,score)构造器被调用");
}
}
//子类
public class Pupil extends Student {
public Pupil(){//
super("smith",18,90.0); //父类构造器在这里被调用
System.out.println("子类Pupil()无参构造器被调用");
}
}
父类Student(name,age,score)构造器被调用
子类Pupil()无参构造器被调用
④ super的放置限制
-
super在使用时,必须放在子类构造器的第一行;(super只能在构造器中使用)
-
super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器;
⑤ object类
java所有类都是Object类的子类,Object是所有类的基类;
⑥ super多层继承时的调用顺序
-
父类构造器的调用不限于直接父类,将一直往上追溯直到Object类(顶级父类),从上向下依次调用;
-
即:A是B的父类,B是C的父类,那么C可以调用A
-
并且在构造器里,会从顶级类一层层往下调用
-
即:A构造器被调用->B构造器被调用->C构造器被调用
⑦ 单继承机制
- 子类最多只能继承一个父类(指直接继承),即java中是单继承机制;
思考:如何让A类继承B类和C类?
答:可以让A类继承B类,B类继承C类,则A可以用B和C中的方法,相当于让A继承了B和C;
⑧ 继承必须满足逻辑关系
不能滥用继承,子类和父类之间必须满足is - a(包含) 的逻辑关系;
Person is a Music? //人不属于音乐,音乐也不属于人,不能建立继承关系
Person Music //不合理
Music extends Person //不合理
(5)继承的本质分析⭐⭐⭐⭐⭐
- 案例
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();
}
}
class GrandPa { //爷爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa { //父类
String name = "大头爸爸";
int age = 39;
}
class Son extends Father { //子类
String name = "大头儿子";
}
//访问顺序(调用属性或者方法的顺序)
- 查看子类是否有该属性
- 如果子类有这个属性,并且可以访问,则返回信息
- 如果子类没有,就看父类,如果有,并且可以访问,就返回信息
- 一直从下级往上级找,直到object或者找到后返回了信息
- 如果要查找的属性在中间层级是私有的,在高层级是公有的。不会跳过中间层级,而会报错
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();
//按照查找关系来返回信息
System.out.println(son.name); //大头儿子
System.out.println(son.getAge());//39 age不能直接访问
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 = "大头儿子";
}
(6)练习
- 编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
- 编写PC子类,继承Computer类,添加特有属性【品牌brand】
- 编写NotePad子类,继承Computer类,添加特有属性【color】
- 编写Test类,在main方法中创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用getInfo方法打印输出信息
//测试
public class Test {
public static void main(String[] args) {
PC pc = new PC("3060",8,128,"intel");
NotePad notePad = new NotePad("3090",16,512,"red");
pc.getInfo();
notePad.getInfo();
}
}
//PC
public class PC extends Computer{
private String brand;
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
public void getInfo(){
System.out.println("====PC信息====");
getDetails();
System.out.println("PC品牌:" + brand);
}
}
//NotePad
public class NotePad extends Computer{
private String color;
public NotePad(String cpu, int memory, int disk, String color) {
super(cpu, memory, disk);
this.color = color;
}
public void getInfo(){
System.out.println("====NotePad信息====");
getDetails();
System.out.println("NotePad颜色:" + color);
}
}
3、多态
(1)引入
解决代码的复用性
(2)基本介绍
方法和对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承的基础上的。
(3)具体表现
- 方法的多态
- 方法的重载体现多态
System.out.println(a.sum(10,20));
System.out.println(a.sum(10,20,30));
- 方法的重写体现多态
a.say();
b.say();
- 对象的多态⭐⭐⭐⭐⭐
- 一个对象的编译类型和运行类型可以不一致;
Dog extends Animal
Animal animal = new Dog();
//父类的对象引用(对象名字,在栈里)可以指向子类的对象(在堆里)
//animal 编译类型是Animal,运行类型Dog(其中Animal是Dog的父类)
- 编译类型在定义对象时,就确定了,不能改变;
Animal animal = new Dog();
animal类型是Animal不能改变
- 运行类型是可以改变的;
animal = new Cat();
即:对象引用的指向是可以改变的,不指向Dog对象,指向Cat类型
编译类型看定义时 = 号的左边,运行类型看 = 号的右边
(3)例子
//类关系
Cat extends Animal;
Dog extends Animal;
//调用方法
Cat cat = new Cat();
Dog dog = new Dog();
Person person = new Person();
person.touch(cat);
person.touch(dog);
//方法定义--只需要传入父类的参数,可以接收父类下面所有子类对象
public void touch(Animal animal,Food food){
System.out.println("主人"+name+"摸"+animal.getName());
}
(4)多态注意事项和使用细节
多态的前提是:两个对象(类)存在继承关系;
- 多态的向上转型:
- 本质:父类的引用指向(接收)了子类的对象;
- 语法:父类类型 引用名 = new 子类类型();
- 特点如下:
- 可以调用父类中的所有成员(需要遵循访问权限);
- 不能调用子类的特有的成员,因为在编译阶段,能够调用哪些成员,是由编译类型来决定的;
- 最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,规则和前面讲的方法调用规则一致。
先看子类->然后看父类->一直向上找
- 多态的向下转型
- 语法:
子类类型 引用名 = (子类类型)父类引用
;
//实例:
Animal animal = new Cat();
Cat cat = (Cat) animal;
//调用,此时能够调用子类cat的特有方法
cat.catchMouse();
- 只能强转父类的引用,不能强转父类的对象;
- 要求父类的引用必须指向的是当前目标类型(即等号前的子类类型)的对象;
相当于animal原来指向一个Cat对象
现在创建了个新名字(新引用),还是指向Cat(通过animal来引用)
//实例:
Animal animal = new Cat();
Dog dog = (Dog) animal;//报错,因为animal指向cat,新的编译类型是Dog,和cat没有继承关系,不能指向cat
-
当向下转型后,可以调用子类类型中所有的成员。
-
属性没有重写(覆盖)之说,属性的值看编译类型;
也就是只会查找父类的
class Sub extends Base;
Base base = new Sub();
System.out.println(base.count); //输出父类Base的count属性值
- instanceof 比较操作符:用于判断对象aa的运行类型是否为BB类型或者BB类型的子类型。aa是否包含于BB。返回值为true/false
例如:aa extends BB
则aa instanceof BB => true;
(5)练习
例子2:
Object obj = 13.4 //ok,double型先自动装箱成Double类,然后向上转型
int objn = (int)obj //ok,obj原先指向Double,先自动拆箱转为doule,然后强制转换为int,这里没有发生对象的向下转型
- 子类对象向上转型为父类对象,OK
- 父类对象再想向下转型,只能转为原先的子类对象或者子类对象的子类对象。(只能往更低处转,不能同级转也不能往高处转)
(6)多态的应用
① 多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
实例:现有一个继承结构如下,要求创建1个Person对象,2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象的say方法。
//主类
public class PolyArray {
public static void main(String[] args) {
//先创建一个Person数组对象
Person[] persons = new Person[5];
//向上转型,persons[i]的编译类型是Person,运行类型根据实际情况由jvm的判断
//对每一个数组元素,都要new一个对象
persons[0] = new Person("hack",30);
persons[1] = new Student("jack1",31,60);
persons[2] = new Student("jack2",35,80);
persons[3] = new Teacher("mark1",50,9000);
persons[4] = new Teacher("mark2",55,10000);
for (int i = 1; i < persons.length; i++) {
//动态绑定机制,从子类(对象的运行类型)开始找say
System.out.println(persons[i].say());
//调用子类的特有方法,不能用父类的编译类型去调用。因为编译这一步就过不去(编译的时候,用的是编译类型的对象去调用)
//persons[i].teach(); //报错
//persons[i].study(); //报错
if(persons[i] instanceof Student){ //persons[i]是否为Student类型(或者其子类)
Student student = (Student)persons[i]; //向下转型
student.study();
//上面两行等价于:((Student)persons[i]).study
} else if(persons[i] instanceof Teacher){//persons[i]是否为Teacher类型(或者其子类)
((Teacher)persons[i]).teach;
} else if(persons[i] instanceof Person){
//
} else {
System.out.println("你的类型有误")
}
}
}
}
//Person类
public class Person {
private String name = "li";
private int age = 23;
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 String say(){
return name + "\t" + age;
}
}
//Student类
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;
}
@Override
//重写父类say()
public String say() {
return super.say() + "\t" + score;
}
public void study(){
System.out.println("学生" + getName() + "正在听课...");
}
}
//Teacher类
public class Teacher extends Person{
private double sal;
public Teacher(String name, int age, double sal) {
super(name, age);
this.sal = sal;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
@Override
public String say() {
return super.say() + "\t" + sal;
}
public void teach(){
System.out.println("老师" + getName() + "正在讲课...");
}
}
② 多态参数
-
定义员工类Employee,包含姓名和月工资【private】,以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法;
-
测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法;
-
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法。
-
Employee.java
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getAnnual(){
return salary*12;
}
}
- ComEmployee.java
public class ComEmployee extends Employee{
public ComEmployee(String name, double salary) {
super(name, salary);
}
public void work(){
System.out.println("普通员工" + getName() + "正在工作");
}
@Override
public double getAnnual() {
return super.getAnnual();
}
}
- Manager.java
public class Manager extends Employee{
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public void manage(){
System.out.println("经理"+getName()+"正在管理员工");
}
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
}
- Test.java
public class Test {
public static void main(String[] args) {
Employee[] employees = new Employee[3];
employees[0] = new ComEmployee("pu1",12.5);
employees[1] = new Manager("man1",20,2);
employees[2] = new ComEmployee("pu2",10);
Test test = new Test();
for (int i = 0; i < 3; i++) {
System.out.println(employees[i].getName() + "年薪有:" +
test.showEmpAnnual(employees[i]) + "k");
test.testWork(employees[i]);
}
}
public double showEmpAnnual(Employee e){
return e.getAnnual();
}
public void testWork(Employee e){
if(e instanceof ComEmployee){
((ComEmployee)e).work();
} else if(e instanceof Manager){
((Manager)e).manage();
} else {
System.out.println("出错");
}
}
}
}