Polimorfismo de herencia de encapsulación básica de Java

¡Continúe creando, acelere el crecimiento! Este es el segundo día de mi participación en el "Nuggets Daily New Plan · June Update Challenge", haz clic para ver los detalles del evento

Encapsulación, herencia y polimorfismo

Encapsulación, herencia y polimorfismo son las tres características de la programación orientada a objetos.

paquete

El propósito de la encapsulación es garantizar la seguridad de las variables. Los usuarios no necesitan preocuparse por los detalles de implementación específicos, sino que solo deben acceder a los miembros de la clase a través de la interfaz externa. Si no se realiza la encapsulación, las variables de instancia en la clase pueden El código tiene una mala influencia, por lo que al escribir una clase, las variables miembro generalmente se privatizan, y la clase externa necesita los mismos métodos getter y setter para ver y establecer las variables.

Suposición: el estudiante Xiao Ming se ha creado con éxito, ¿puede cambiar su nombre y edad a voluntad en circunstancias normales?

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;
    }
}
复制代码

Es decir, el exterior solo puede obtener las propiedades de los miembros llamando al método que definí, y podemos realizar algunas operaciones adicionales en este método. Por ejemplo, Xiao Ming puede cambiar el nombre, pero el nombre no puede contener la palabra "Xiao". .

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

El método para establecer el nombre de la apertura externa es independiente, porque también necesito realizar un procesamiento adicional, ¡así que no puedo otorgar permiso externo para manipular directamente las variables miembro!

La idea de la encapsulación es en realidad ocultar los detalles de la implementación, y el exterior solo necesita saber qué hace este método, sin preocuparse por la implementación.

La encapsulación se logra a través del control de acceso.

heredar

La herencia es muy importante. Hay algunos atributos comunes al definir diferentes clases. Por comodidad, estos atributos comunes se pueden abstraer en una clase principal. Al definir otras subclases, puede heredar de la clase principal para reducir la definición repetitiva del código. las subclases pueden usar miembros no privados de la superclase.

Ahora los estudiantes se dividen en dos tipos, estudiantes de arte y estudiantes de deportes, todos son ramas de estudiantes, pero todos tienen sus propios métodos:

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("随手画个毕加索!");
    }
}
复制代码

La subclase tiene todas las propiedades de la clase principal, protected es visible pero no se puede usar externamente (incluidas privatelas propiedades, es invisible y no se puede usar), y la subclase también puede tener sus propios métodos. La herencia solo puede heredar de una clase principal, ¡la herencia múltiple no es compatible!

Cada subclase debe definir un constructor que implemente el constructor de la clase principal, es decir, debe usarse al principio del constructor.Si super()la clase principal usa el constructor predeterminado, la subclase no necesita especificarse manualmente.

所有类都默认继承自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多线程中再来提及。

多态

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

方法的重写

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

//父类中的study
public 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关键字来实现,它能够进行类型判断!

private static void test(Student student){
    if (student instanceof SportsStudent){
        SportsStudent sportsStudent = (SportsStudent) student;
        sportsStudent.sport();
    }else if (student instanceof ArtStudent){
        ArtStudent artStudent = (ArtStudent) student;
        artStudent.art();
    }
}
复制代码

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

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

再谈final关键字

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

public final class Student {   //类被声明为终态,那么它还能被继承吗
    
}
复制代码

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

public final void study(){  //还能重写吗
    System.out.println("学习");
}
复制代码

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

private final String name;   //引用类型不允许再指向其他对象
private final int age;    //基本类型值不允许发生改变

public Student(String name, int age) {
    this.name = name;
    this.age = age;
}
复制代码

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

抽象类

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

public abstract class Student {    //抽象类
        public abstract void test();  //抽象方法
}
复制代码

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

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

Student s = new Student(){    //只能直接创建带实现的匿名内部类!
  public void test(){
    
  }
}
复制代码

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

public void study(){   //现在只能由子类编写,父类没有定义,更加明确了多态的定义!同一个方法多种实现!
    System.out.println("给你看点好康的");
}
复制代码

接口

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

public interface Eat {
    void eat(); 
}
复制代码

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

public interface Eat {
    default void eat(){
        //do something...
    }
}
复制代码

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

public interface Eat {
    int a = 1;
    void eat();
}
复制代码

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

public class SportsStudent extends Student implements Eat, ...{
        @Override
    public void eat() {
        
    }
}
复制代码

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

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

内部类

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

成员内部类

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

public class Test {
    class Inner{   //类中定义的一个内部类
        
    }
}
复制代码

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

public static void main(String[] args) {
    Test test = new Test();
    Test.Inner inner = test.new Inner();   //写法有那么一丝怪异,但是没毛病!
}
复制代码

静态内部类

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

public class Test {
    static class Inner{

    }
}

public static void main(String[] args) {
    Test.Inner inner = new Test.Inner();   //不用再创建外部类对象了!
}
复制代码

局部内部类

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

public class Test {
    public void test(){
        class Inner{

        }
        
        Inner inner = new Inner();
    }
}
复制代码

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

匿名内部类

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

public static void main(String[] args) {
        Eat eat = new Eat() {
            @Override
            public void eat() {
                //DO something...
            }
        };
    }
复制代码

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

lambda表达式

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

public static void main(String[] args) {
        Eat eat = new Eat() {
            @Override
            public void eat() {
                //DO something...
            }
        };
    }

public static void main(String[] args) {
        Eat eat = () -> {};   //等价于上述内容
    }
复制代码

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

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

枚举类

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

public class Student {
    private final String name;
    private final int age;
    private String status;
  
    //...
  
    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }
}
复制代码

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

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

public enum Status {
    RUNNING, STUDY, SLEEP    //直接写每个状态的名字即可,分号可以不打,但是推荐打上
}
复制代码

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

public class Student {
    private final String name;
    private final int age;
    private Status status;
  
        //...
  
    public void setStatus(Status status) {   //不再是String,而是我们指定的枚举类型
        this.status = status;
    }

    public Status getStatus() {
        return status;
    }
}

public static void main(String[] args) {
    Student student = new Student("小明", 18);
    student.setStatus(Status.RUNNING);
    System.out.println(student.getStatus());
}
复制代码

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

// Compiled from "Status.java"
public final class com.test.Status extends java.lang.Enum<com.test.Status> {
  public static final com.test.Status RUNNING;
  public static final com.test.Status STUDY;
  public static final com.test.Status SLEEP;
  public static com.test.Status[] values();
  public static com.test.Status valueOf(java.lang.String);
  static {};
}
复制代码

Dado que el tipo de enumeración es una clase normal, también podemos agregar un método de miembro único al tipo de enumeración

public enum Status {
    RUNNING("睡觉"), STUDY("学习"), SLEEP("睡觉");   //无参构造方法被覆盖,创建枚举需要添加参数(本质就是调用的构造方法!)

    private final String name;    //枚举的成员变量
    Status(String name){    //覆盖原有构造方法(默认private,只能内部使用!)
        this.name = name;
    }
  
    public String getName() {   //获取封装的成员变量
        return name;
    }
}

public static void main(String[] args) {
    Student student = new Student("小明", 18);
    student.setStatus(Status.RUNNING);
    System.out.println(student.getStatus().getName());
}
复制代码

La clase de enumeración también viene con algunos métodos de utilidad heredados.

Status.valueOf("")   //将名称相同的字符串转换为枚举
Status.values()   //快速获取所有的枚举
复制代码

Supongo que te gusta

Origin juejin.im/post/7101853827787620388
Recomendado
Clasificación