首页>>后端>>java->Java基础封装继承多态

Java基础封装继承多态

时间:2023-12-06 本站 点击:0

封装、继承和多态

封装、继承和多态是面向对象编程的三大特性。

封装

封装的目的是为了保证变量的安全性,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员,如果不进行封装,类中的实例变量可以直接查看和修改,可能给整个代码带来不好的影响,因此在编写类时一般将成员变量私有化,外部类需要同getter和setter方法来查看和设置变量。

设想:学生小明已经创建成功,正常情况下能随便改他的名字和年龄吗?

public class Student {    private String name;    private int age;      public Student(String name, int age) {        this.name = name;        this.age = age;    }    public int getAge() {        return age;    }    public String getName() {        return name;    }}

也就是说,外部现在只能通过调用我定义的方法来获取成员属性,而我们可以在这个方法中进行一些额外的操作,比如小明可以修改名字,但是名字中不能包含"小"这个字。

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}

单独给外部开放设置名称的方法,因为我还需要做一些额外的处理,所以说不能给外部直接操作成员变量的权限!

封装思想其实就是把实现细节给隐藏了,外部只需知道这个方法是什么作用,而无需关心实现。

封装就是通过访问权限控制来实现的。

继承

继承属于非常重要的内容,在定义不同类的时候存在一些相同属性,为了方便使用可以将这些共同属性抽象成一个父类,在定义其他子类时可以继承自该父类,减少代码的重复定义,子类可以使用父类中非私有的成员。

现在学生分为两种,艺术生和体育生,他们都是学生的分支,但是他们都有自己的方法:

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}

子类具有父类的全部属性,protected可见但外部无法使用(包括private属性,不可见,无法使用),同时子类还能有自己的方法。继承只能继承一个父类,不支持多继承!

每一个子类必须定义一个实现父类构造方法的构造方法,也就是需要在构造方法开始使用super(),如果父类使用的是默认构造方法,那么子类不用手动指明。

所有类都默认继承自Object类,除非手动指定类型,但是依然改变不了最顶层的父类是Object类。所有类都包含Object类中的方法,比如:

public static void main(String[] args) {Object obj = new Object;System.out.println(obj.hashCode());  //求对象的hashcode,默认是对象的内存地址System.out.println(obj.equals(obj));  //比较对象是否相同,默认比较的是对象的内存地址,也就是等同于 ==System.out.println(obj.toString());  //将对象转换为字符串,默认生成对象的类名称+hashcode}

关于Object类的其他方法,我们会在Java多线程中再来提及。

多态

多态是同一个行为具有多个不同表现形式或形态的能力。也就是同样的方法,由于实现类不同,执行的结果也不同!

方法的重写

我们之前学习了方法的重载,方法的重写和重载是不一样的,重载是原有的方法逻辑不变的情况下,支持更多参数的实现,而重写是直接覆盖原有方法!

//父类中的studypublic void study(){    System.out.println("学习");}//子类中的study@Override  //声明这个方法是重写的,但是可以不要,我们现阶段不接触public void study(){    System.out.println("给你看点好康的");}

再次定义同样的方法后,父类的方法就被覆盖!子类还可以给父类方法提升访问权限!

public static void main(String[] args) {     SportsStudent student = new SportsStudent("lbw", 20);     student.study();   //输出子类定义的内容}

思考:静态方法能被重写吗?

当我们在重写方法时,不仅想使用我们自己的逻辑,同时还希望执行父类的逻辑(也就是调用父类的方法)怎么办呢?

public void study(){    super.study();    System.out.println("给你看点好康的");}

同理,如果想访问父类的成员变量,也可以使用super关键字来访问,注意,子类可以具有和父类相同的成员变量!而在方法中访问的默认是 形参列表中 > 当前类的成员变量 > 父类成员变量

public void setTest(int test){    test = 1;    this.test = 1;    super.test = 1;}

再谈类型转换

我们曾经学习过基本数据类型的类型转换,支持一种数据类型转换为另一种数据类型,而我们的类也是支持类型转换的(仅限于存在亲缘关系的类之间进行转换)比如子类可以直接向上转型:

Student student = new SportsStudent("lbw", 20);  //父类变量引用子类实例student.study();     //得到依然是具体实现的结果,而不是当前类型的结果

我们也可以把已经明确是由哪个类实现的父类引用,强制转换为对应的类型:

Student student = new SportsStudent("lbw", 20);  //是由SportsStudent进行实现的//... do something...SportsStudent ps = (SportsStudent)student;  //让它变成一个具体的子类ps.sport();  //调用具体实现类的方法

这样的类型转换称为向下转型。

instanceof关键字

那么我们如果只是得到一个父类引用,但是不知道它到底是哪一个子类的实现怎么办?我们可以使用instanceof关键字来实现,它能够进行类型判断!

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}0

通过进行类型判断,我们就可以明确类的具体实现到底是哪个类!

思考:student instanceof Student的结果是什么?

再谈final关键字

我们目前只知道final关键字能够使得一个变量的值不可更改,那么如果在类前面声明final,会发生什么?

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}1

类一旦被声明为终态,将无法再被继承,不允许子类的存在!而方法被声明为final呢?

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}2

如果类的成员属性被声明为final,那么必须在构造方法中或是在定义时赋初始值!

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}3

学习完封装继承和多态之后,我们推荐在不会再发生改变的成员属性上添加final关键字,JVM会对添加了final关键字的属性进行优化!

抽象类

类本身就是一种抽象,而抽象类,把类还要抽象,也就是说,抽象类可以只保留特征,而不保留具体呈现形态,比如方法可以定义好,但是我可以不去实现它,而是交由子类来进行实现!

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}4

通过使用abstract关键字来表明一个类是一个抽象类,抽象类可以使用abstract关键字来表明一个方法为抽象方法,也可以定义普通方法,抽象方法不需要编写具体实现(无方法体)但是必须由子类实现(除非子类也是一个抽象类)!

抽象类由于不是具体的类定义,因此无法直接通过new关键字来创建对象!

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}5

因此,抽象类一般只用作继承使用!抽象类使得继承关系之间更加明确:

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}6

接口

接口甚至比抽象类还抽象,他只代表某个确切的功能!也就是只包含方法的定义,甚至都不是一个类!接口包含了一些列方法的具体定义,类可以实现这个接口,表示类支持接口代表的功能(类似于一个插件,只能作为一个附属功能加在主体上,同时具体实现还需要由主体来实现)

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}7

通过使用interface关键字来表明是一个接口(注意,这里class关键字被替换为了interface)接口只能包含public权限的抽象方法!(Java8以后可以有默认实现)我们可以通过声明default关键字来给抽象方法一个默认实现:

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}8

接口中定义的变量,默认为public static final

public void setName(String name) {    if(name.contains("小")) return;    this.name = name;}9

一个类可以实现很多个接口,但是不能理解为多继承!(实际上实现接口是附加功能,和继承的概念有一定出入,顶多说是多继承的一种替代方案)一个类可以附加很多个功能!

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}0

类通过implements关键字来声明实现的接口!每个接口之间用逗号隔开!

实现接口的类也能通过instanceof关键字判断,也支持向上和向下转型!

内部类

类中可以存在一个类!各种各样的长相怪异的代码就是从这里开始出现的!

成员内部类

我们的类中可以在嵌套一个类:

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}1

成员内部类和成员变量和成员方法一样,都是属于对象的,也就是说,必须存在外部对象,才能创建内部类的对象!

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}2

静态内部类

静态内部类其实就和类中的静态变量和静态方法一样,是属于类拥有的,我们可以直接通过类名.去访问:

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}3

局部内部类

对,你没猜错,就是和局部变量一样哒~

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}4

反正我是没用过!内部类 -> 累不累 -> 反正我累了!

匿名内部类

匿名内部类才是我们的重点,也是实现lambda表达式的原理!匿名内部类其实就是在new的时候,直接对接口或是抽象类的实现:

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}5

我们不用单独去创建一个类来实现,而是可以直接在new的时候写对应的实现!但是,这样写,无法实现复用,只能在这里使用!

lambda表达式

读作λ表达式,它其实就是我们接口匿名实现的简化,比如说:

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}5public static void main(String[] args) {        Eat eat = () -> {};   //等价于上述内容    }

lambda表达式(匿名内部类)只能访问外部的final类型或是隐式final类型的局部变量!

为了方便,JDK默认就为我们提供了专门写函数式的接口,这里只介绍Consumer

枚举类

假设现在我们想给小明添加一个状态(跑步、学习、睡觉),外部可以实时获取小明的状态:

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}7

但是这样会出现一个问题,如果我们仅仅是存储字符串,似乎外部可以不按照我们规则,传入一些其他的字符串。这显然是不够严谨的!

有没有一种办法,能够更好地去实现这样的状态标记呢?我们希望开发者拿到使用的就是我们定义好的状态,我们可以使用枚举类!

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}8

使用枚举类也非常方便,我们只需要直接访问即可

public class SportsStudent extends Student{   //通过extends关键字来继承父类    public SportsStudent(String name, int age) {        super(name, age);   //必须先通过super关键字(指代父类),实现父类的构造方法!    }    public void exercise(){        System.out.println("我超勇的!");    }}public class ArtStudent extends Student{    public ArtStudent(String name, int age) {        super(name, age);    }    public void art(){        System.out.println("随手画个毕加索!");    }}9

枚举类型使用起来就非常方便了,其实枚举类型的本质就是一个普通的类,但是它继承自Enum类,我们定义的每一个状态其实就是一个public static final的Status类型成员变量!

public static void main(String[] args) {Object obj = new Object;System.out.println(obj.hashCode());  //求对象的hashcode,默认是对象的内存地址System.out.println(obj.equals(obj));  //比较对象是否相同,默认比较的是对象的内存地址,也就是等同于 ==System.out.println(obj.toString());  //将对象转换为字符串,默认生成对象的类名称+hashcode}0

既然枚举类型是普通的类,那么我们也可以给枚举类型添加独有的成员方法

public static void main(String[] args) {Object obj = new Object;System.out.println(obj.hashCode());  //求对象的hashcode,默认是对象的内存地址System.out.println(obj.equals(obj));  //比较对象是否相同,默认比较的是对象的内存地址,也就是等同于 ==System.out.println(obj.toString());  //将对象转换为字符串,默认生成对象的类名称+hashcode}1

枚举类还自带一些继承下来的实用方法

public static void main(String[] args) {Object obj = new Object;System.out.println(obj.hashCode());  //求对象的hashcode,默认是对象的内存地址System.out.println(obj.equals(obj));  //比较对象是否相同,默认比较的是对象的内存地址,也就是等同于 ==System.out.println(obj.toString());  //将对象转换为字符串,默认生成对象的类名称+hashcode}2
原文:https://juejin.cn/post/7101853827787620388


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/15947.html