Java中的三大特性之封装,继承,多态

目录

一.封装

1.封装的引入

2.封装的概念:

3.访问限定符

(1)Java中如何进行封装

(2)代码验证访问权限

(3)关于如何使用修饰符封装

3.封装之包的使用

 (1)包的引入

(2)包的概念

(3)Java中自带包的使用

 (4)Java中常见的包

(5)如何自定义包

二.继承

1.什么是继承

2.为什么要有继承

3.继承的概念

4.如何来实现继承

5.继承中的注意事项

 6.访问父类成员时的注意事项

(1)父类和子类成员变量名字相同时

(2)成员方法名相同

 (3)成员变量名相同时,访问父类的成员变量(super关键字)

(4)总结

7.子类的构造方法

(1)构造方法的调用

 (2)super和this的异同点

8.代码块在继承中的执行次序

(1)引入

(2)代码验证

 (3)总结

9.Java中支持的继承方式

10.final关键字

 11.继承与组合(代码重用)

三.多态

1.多态的概念

2.多态的实现条件

3.代码(上课举例)

 4.方法的重写与重载

(1)方法的重写

 (2)方法的重载

5.向上转型和向下转型

(1)什么是向上转型

(2)代码测试

(3)什么是向下转型


一.封装

1.封装的引入

一听到封装,首先想到的是某个东西被包装起来,例如一个盒子中的东西被包裹起来使其里面的东西不被被人看到。

这里来举个更详细的例子,那就是电脑,我们买的台式电脑,一般我们就只知道如何开机,使用键盘和鼠标来和计算机进行交互,但是计算机真正运行的时候是通过cpu,显卡,内存等一些硬件设备来进行运行,但是我们在使用的时候是看不到这些东西的,因为这些东西被厂商给封装起来,只显露出用户基本操作的东西。

2.封装的概念:

将数据和操作数据的方法进行有机结合,隐藏对象的属性(成员变量)和实现细节,仅对外公开接口来和对象进行交互。

3.访问限定符

在使用封装之前我们首先要了解访问限定符,这样才可以使用封装。

(1)Java中如何进行封装

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制那些方法或者属性需要在类外直接使用。Java中提供了四种访问限定符如下:

(2)代码验证访问权限

 在同一包同一类中:

 

 在同一包不同类中

 在不同包的不同类中

 

 在不同包的子类中

注意:一般情况下成员变量设置为private,成员方法设置为public。

(3)关于如何使用修饰符封装

一般会将成员变量用private修饰,成员方法会用public来修饰。但是在具体使用中还是要看该类中的成员变量要给什么类进行使用,如果是子类(在后面继承中有什么是子类),就用protected;如果该成员变量需要给所有类都要访问,就设置为public;只有合适地使用修饰符,成员才能有好的封装性。

3.封装之包的使用

 (1)包的引入

Java中为了更好的管理类,所以引入了包。

(2)包的概念

把多个类或者收集在一起成为一组,称为软件包。

在Java中,包相当于是文件夹,来对类进行管理。包相当于是对类和接口等封装的体现,例如一个包中的类不想被其他包中的类访问,要是不导入该报是无法进行使用。而且名称相同的类可以在不同的包下存在。


(3)Java中自带包的使用

在Java中有很多现成的类可以供我们使用,但是在使用之前需要我们导入相关的包才能进行使用。例如,Arrays类,Date类这些都是通过导包后直接进行使用,用法和我们自己定义的类是一样的,访问成员方法通过.来进行访问。

下面是如何进行导包和使用的。

方法1:在将包的路径加到对象前

public class Demo {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        System.out.println(date.getTime());//打印时间的毫秒数
    }
}

方法2:通过import关键字在类的外面来进行导包

import java.util.Date;

public class Demo {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date.getTime());//打印时间的毫秒数
    }
}

这两种方法哪种比较好要看使用的场景。

注意:如果你导入了多个包,在另一个包中也包含相同名称的类,Java编译器可能不知道你引入的是哪个包,这时候使用方法1(在将包的路径加到对象前);如果没有名称相同的类,那么为了使用方便,你可以使用第二种方法。

如果你需要util包下的多个类,这时候你可以使用import java.util.*;直接都导入进来使用。

这里我同时使用Date类和Arrays类(这两个类都在util包下),通过导包来进行使用:

 (4)Java中常见的包

1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 重要
6. java.io:I/O编程开发包


(5)如何自定义包

既然Java中有包的概念,那么自己也可以进行自定义包。

具体操作:

 关于给包命名规定:在大型开发中,不同公司为了防止包名相同,一般都会以公司的域名颠倒加包名来定义,例如:com.baidu.demo。

在创建好包后,会在自己当前工程下生成一个个文件夹,并且在包下创建的类会在首行加入package +包名。

二.继承

1.什么是继承

在生活中,我们有很多继承的例子,例如儿子继承父亲的财产,猫和狗继承了动物的一些特性。

2.为什么要有继承

如果我们定义一个猫类和一个狗类我们会想到它们都有哪些属性,如颜色,名字,公母等属性,它们都会睡觉,吃饭。这些都是它们属于动物的共性,如果分开来写,这些相当于都是些重复的代码,因此我们想到了把它们这些共性放到同一个动物类中,这不是不用我们做重复工作了。

3.继承的概念

是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类(其他名字:父类,基类,超类)特性
的基础上进行扩展,增加新功能,这样产生新的类,称派生类(子类)。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

4.如何来实现继承

使用方法: 子类 extends 父类

接下来就使用猫和狗类来进行举例说明:

(1)动物类

public class Animal {
    String name;//名字
    String color;//颜色
    String gender;//公母
    public void sleep(){
        System.out.println(name+"睡觉");
    }
    public void eat(){
        System.out.println(name+"吃东西");
    }
}

(2)猫类 

public class Cat extends Animal{
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.name="小菊";
        cat.gender="母";
        cat.color= "橘色";
        cat.eat();
        cat.sleep();
    }
}

 (3)狗类

public class Dog extends Animal{
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "小黑";
        dog.gender="公";
        dog.color = "黑色";
        dog.eat();
        dog.sleep();
    }
}

猫类运行结果:

 狗类运行结果:

 从上述代码可以看出,我们没有在狗类和猫类中定义这些属性和方法,但是就可以通过实例化对象来进行访问,这是因为我们继承了动物类中的成员变量成员方法,在这里面,动物类就相当于狗类和猫类的父类,而这两个类本身相当于子类。

5.继承中的注意事项

(1)子类只会将父类的成员变量和成员方法继承到。

(2)子类在继承父类后,必须要有自己特有成员,否则就没有继承的必要了。

上面的狗类和猫类我们可以添加特定的成员方法来进行实现,在狗类中添加一个看门的方法,在猫类中添加一个抓老鼠的方法。

如下所示:

 

 6.访问父类成员时的注意事项

我们知道,子类继承了父类的成员变量和方法,但是有时候我们一不小心在子类中也定义和父类相同名字的成员后,这时候就需要知道Java是该使用哪个和如何进行父类中成员的使用。

(1)父类和子类成员变量名字相同时

(2)成员方法名相同

 方法名字相同且里面的参数列表也相同时,会根据就近原则访问子类的方法;但当方法名同,参数列表不同,会形成方法的重载,根据调用方法传递的参数不同来具体访问指定的方法。

 

 (3)成员变量名相同时,访问父类的成员变量(super关键字)

要访问父类同名的成员变量,通过super关键字来进行实现。

super关键字的目的是:在子类的非静态方法中访问父类的成员。

父类:

public class Fu {
    int a=1;
    int b=2;
public void testMethod(){
    System.out.println("父");
}
}

子类:

public class Zi extends Fu{
    int a=10;
    int b=20;
    int c = 3;
    public void testTemp(){
    super.a=10;
    super.b=20;
    c=30;
    }
    public void testMethod(int count){
        System.out.println("子");
    }
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.testTemp();
        zi.testMethod();
        zi.testMethod(1);
    }
}

 super关键字注意事项:

只能在非静态方法中使用

在子类方法中只能访问父类的成员变量和方法

(4)总结

在访问过程中,会优先在子类中进行查找,如果找到就进行访问,如果没有找到,就到父类中进行查找,找到了就访问,没有找到就会报错。

在访问过程中如果同名后直接使用,会根据就近原则来进行访问。方法稍微有些区别。

7.子类的构造方法

(1)构造方法的调用

在继承概念中,我们知道子类只继承了父类的成员变量和方法,那么构造方法是如何定义的。

在构造子类对象时会先调用子类的构造方法,在子类的构造方法中,第一句就是调用父类的构造方法,通过代码来进行验证。

 我们这里没有构造父类对象,编译器为什么会调用父类的构造方法呢,这是因为子类对象也是一个父类对象,在构造子类对象时,先要将从父类继承下来的成员初始化完整,然后再初始化子类自己新增加的成员。

通过对象模型来简单说明:

对于自动调用父类构造方法的前提是父类的构造方法是无参的,如果有参数,那么编译器是无法自动地进行调用,这时候需要我们手动调用并且传递给指定的参数。

如下所示:

未手动调用:

 手动调用:

 (2)super和this的异同点

 1.相同点

  1. 都是Java中的关键字。
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段。
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。

2.不同点

  1. 引用对象不同:this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是父类对象的引用。
  2. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是父类对象的引用。
  3. this是非静态成员方法的一个隐藏参数,super不是隐藏的参数。
  4. 成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的。
  5. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现。
  6. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。

8.代码块在继承中的执行次序

(1)引入

在类和对象中,我们知道代码块中静态代码块先执行,且无论创建多少个对象都只执行一次,接着是实例代码块执行,最后是构造方法执行。那么在继承体系中是如何进行执行的。

(2)代码验证

public class Test {
    public static void main(String[] args) {
    Student student1 = new Student("张三",18,"男");
        System.out.println("################");
        Student student2 = new Student("小红",16,"女");


    }
}
class Person{
    String name;
    int age;
    String gender;
    {
        System.out.println("Person实例代码块");
    }
    static{
        System.out.println("Person静态代码块");
    }
    public Person(String name, int age, String gender) {
        System.out.println("Person构造器");
    }
}
class Student extends Person{
    {
        System.out.println("Student实例代码块");
    }
    static {
        System.out.println("Student静态代码块");
    }
    public Student(String name, int age, String gender) {
        super(name, age, gender);
        System.out.println("Student构造器");
    }
}

运行结果:

 (3)总结

 在继承体系中,运行的次序依次为:父类静态代码块,子类静态代码块(这两个静态代码块都在类加载过程中只执行一次),父类实例代码块,父类构造方法,子类实例代码块,子类构造方法。

9.Java中支持的继承方式

 注意:虽然类可以多层继承,但是一般不超过三层,如果继承层数太多,需要考虑对代码进行重构,因为层次越多,代码结构越复杂。

10.final关键字

(1)final的用途:用来修饰变量,方法,类。

(2)final的作用结果

修饰变量:变量不可以进行修改。

 修饰方法,方法不能重写(重写在多态介绍)

 修饰类,类不能被继承:

 11.继承与组合(代码重用)

 我们已经知道了继承能够实现代码的重用,但是我们一般不推荐使用继承来实现重用,除非类具有is-a的关系:如狗是动物,猫是动物。

在编程过程中我们一般使用组合来实现代码的重用,对于组合来说,就是has -a的关系:如汽车中有轮胎,发动机,方向盘等。很明显,这里汽车和里面的东西没有较强的继承关系,使用继承很明显不合适,所以我们可以使用组合来进行实现,下面以代码来进行举例:

public class BMWCar extends Car{
    String color;
    
}
//汽车类
class Car{
    protected SteeringWheel steeringWheel;
    protected Engine engine;
    public void Start(){
        engine.start();
        steeringWheel.turn();
    }
    public void Stop(){
        engine.stop();
    }

}
//方向盘类
class SteeringWheel{
    public void turn(){
        System.out.println("转动方向盘");
    }

}
//发动机类
class Engine{
    public void start(){
        System.out.println("发动机启动");
    }
    public void stop(){
        System.out.println("发动机熄火");
    }
}

 

 综上,对于代码重用还是首先推荐使用组合。

三.多态

1.多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。例如猫和狗对于吃这种行为,就会产生不同的状态,猫吃猫粮,狗吃狗狗粮;对于去教室这种行为而言,老师是去上课,学生是去听课。因此就产生了多态。

总而言之,就是同一件事情发生在不同对象身上就会产生不同的结果。

2.多态的实现条件

我们既然已经知道了什么是多态,那么我们就需要知道实现多态有哪些条件限制。

(1)需在继承体系下

(2)子类需对父类方法进行重写

(3)通过父类的引用调用重写的方法

3.代码(上课举例)

public class ClassRoom {
    public static void testInClass(Person person){
        person.inClass();
    }
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Pupil pupil = new Pupil();

        //通过给testInClass(person)传递不同对象来调用不同的重写
        testInClass(teacher);
        testInClass(pupil);
    }
}
//老师
class Teacher extends Person{
    public void inClass(){
        System.out.println("Teacher上课");
    }
}
//学生
class Pupil extends Person{
    public void inClass(){
        System.out.println("Pupil上课");
    }
}
//人
class Person{
    public void inClass(){
        System.out.println("Person上课");
    }
}

运行结果:

 在该例子中,通过对父类Person中inClass()方法在子类中的重写,并在testInClass(..)调用inClass()方法,通过不同的对象来实现不同的行为,这种行为就称为多态。

 4.方法的重写与重载

(1)方法的重写

在多态中,我们知道,其中的一个条件就是方法的重写,所以我们要知道什么是方法的重写以及如何实现方法的重写。

1.1方法重写的概念:

也称为覆盖。重写是子类对父类非静态非private修饰非final修饰非构造方法等的实现过程
进行重新编写, 返回值和形参都不能改变只能改变方法中的内容

代码验证:

(1)对于静态方法,直接可以通过类名来进行访问,体现不出重写

(2)priavte修饰的不能被重写

(3)构造方法不能重写

(4)被final修饰不能重写


1.2重写的注意事项:

(1)必须与父类方法一模一样,除了方法中的内容。

(2)在jdk7以后,返回值类型可以是具有父子关系的类类型

(3)子类中重写方法的访问权限修饰符不能比父类方法的权限低。

访问权限由大到小:public>protected>default(不使用private)

 (4)可以通过@Override注释来验证是否被重写。

(5)重写的设计原则;对于已经投入使用的类尽量不要修改,可以定义一个新的类来进行旧类方法中的重写,因为这样对于用户比较友好。

 (2)方法的重载

重载的概念:方法名相同,方法的参数列表不同。

参数列表不同包括:方法中的参数类型不同,相同参数类型的顺序不同,参数的个数不同

由于编译器会自动对实参类型进行推演,根据推演来确定用户调用的是哪个方法,所以可以形成方法的重载。

例如:

public class TestOverload{
    //    test方法参数列表不同,方法名相同,实现了重载
    public void test(){
        
    }
    public void test(int a){
        
    }
}

重载的注意事项:

重载与返回值的类型无关,只有类型不同构成不了重载

 (3)重载与重写的区别

区别重载重写
参数列表可以修改不可以修改
返回值类型可以修改不可以修改(可以修改为有继承关系的类类型)
访问限定符可以修改可以降低限制
异常可以修改可以减少或删除,不能抛出新的或更广的异常(暂时不管)

 总结:对于重载和重写,都体现了多态。

重载体现了一个类的多态,这里重载属于静态绑定

重写体现了子类与父类之间的多态,这里重写属于动态绑定

静态绑定概念:即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。

动态绑定概念:即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体
调用那个类的方法。

5.向上转型和向下转型

方法的重写来实现多态,里面就出现了转型的概念。

(1)什么是向上转型

实际就是创建一个子类对象,将其当成父类对象来使用。

格式:父类类型  对象名  = new 子类类型

(2)代码测试

public class OverrideAccess {
    public static void main(String[] args) {
        Father father = new Son();//这就是向上转型

    }
}
class Father{
    public Father test(){
    return new Father();
    }

}
class Son extends Father{

}

因为子类对象是一个父类对象,子类对象可以当父类对象来使用,而且向上转型是安全的,没有任何问题。

注意:返回值类型也可以使用向上转型:

代码测试:

其中testReturn()体现了向上转型。

public class OverrideAccess {
    public Father testReturn(String str){
        if(str=="Father"){
            return new Father();
        }if(str=="Son"){
            return new Son();
        }
        else {
            return null;
        }
    }
    public static void main(String[] args) {
        

    }
}
class Father{
    public Father test(){
    return new Father();
    }

}
class Son extends Father{

}

虽然向上转型是代码更加灵活了,但是我们就不能访问子类中特有的方法了,只能访问父类中被子类重写的方法。

(3)什么是向下转型

1.概念:就是将父类对象还原为子类对象的过程。

2.注意:我们知道,子类属于父类,可以任意进行向上转型,但是父类不一定就是子类,例如猫是动物,没有问题;但是说动物是猫就会出现问题。

代码举例:

public class ClassRoom {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Pupil pupil = new Pupil();
        Person person  =teacher;//向上转型
        person.inClass();
        person = pupil;
        person.inClass();
        teacher = (Teacher) person;//向下强转
        teacher.work();

    }
}
//老师
class Teacher extends Person{
    public void inClass(){
        System.out.println("Teacher上课");
    }
    public void work(){
        System.out.println("老师写教案");
    }
}
//学生
class Pupil extends Person{
    public void inClass(){
        System.out.println("Pupil上课");
    }
    public void homework(){
        System.out.println("学生写作业");
    }
}
//人
class Person{
    public void inClass(){
        System.out.println("Person上课");
    }
}

 上面中向下转型,出现了安全性问题,Java为了解决这种向下转型的问题引入了instanceof为true来判断是否可以进行向下转型。

用法如下所示:

if(父类对象 instanceof 子类名){

转型的语句,及需要调用子类的方法

}

代码演示:

public class ClassRoom {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Pupil pupil = new Pupil();
        Person person  =teacher;//向上转型
        person.inClass();
        person = pupil;
        person.inClass();
 
        //向下转型修改后的结果
        if(person instanceof Pupil){//判断是否可以向下转型
            pupil = (Pupil) person;
            pupil.homework();
        }
        if(person instanceof Teacher){
            teacher = (Teacher) person;
            teacher.work();
        }

    }
}
//老师
class Teacher extends Person{
    public void inClass(){
        System.out.println("Teacher上课");
    }
    public void work(){
        System.out.println("老师写教案");
    }
}
//学生
class Pupil extends Person{
    public void inClass(){
        System.out.println("Pupil上课");
    }
    public void homework(){
        System.out.println("学生写作业");
    }
}
//人
class Person{
    public void inClass(){
        System.out.println("Person上课");
    }
}

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值