二、面向对象编程

225 阅读13分钟

面向对象编程

万物皆对象

面向对象指以属性和行为的观点去分析现实生活中的事物。

面向过程 & 面向对象

面向过程:讲究把任务拆分成第一步干嘛、第二步干嘛... (我在洗衣服)

面向对象:讲究把任务交给一个对象去做,我只需要考虑它能不能胜任。(女朋友看着我洗衣服)

类和对象

对象:客观存在

类:分类,如分为人类、鸟类、鱼类,是对对象的特征和行为抽象出来的模板(设计图)

对象的创建

  • 创建对象的本质就是在内存空间的堆区申请一块存储区域,用于存放该对象独有特征信息。

引用

  • 就是使用引用数据类型定义的变量。

  • 引用记录对象在堆区中的内存地址信息。

类代码的执行流程

  • Person的class字节码加载到方法区中
  • 将main方法加载到栈中,因为局部变量都保存在栈中,而局部变量在方法里
  • 执行Person p,在栈中声明一个空间
  • 接着执行 = new person(),会将方法区的Person的Class字节码拷贝一份到堆上,并让p指向该堆地址
  • ...

可变长参数

  • ...代表0~n个参数

  • 一个方法形参列表中最多只能声明一个可变长形参,并且需要放到参数列表的末尾

  • 看做一维数组使用即可

内存结构之栈区

  • 栈用于存放程序运行过程中所有的局部变量。

  • JVM每调用一次方法,都会在栈中分配一个对应的空间,来保存方法的字节码、局部变量等信息,这个空间叫做栈帧。

默认构造方法

  • 当一个类没有定义任何构造方法,编译器会自动添加一个无参构造方法。

  • 当有自定义的构造方法,则编译器不再提供任何形式的构造方法。

重载

  • 方法名相同,参数个数或类型或顺序不同

this

  • this代表当前对象的引用,例如Person p = new Person(),那么this就是指p。

封装

public class Package {

    // 封装的好处
    // ①通过private隐藏自己,不再毫无隐私
    // ②通过方法访问,过滤合法但不合理的传参,如年龄不能为负数

    // 封装的实现流程
    // ①私有化成员变量,使用private关键字修饰
    // ②提供公有的get和set方法,并在方法体内进行合理的判断
    // ③在构造方法中调用set方法进行合理的判断

    private int age;

    public Package() {}

    public Package(String name, int age) {
        setAge(age);
    }


    public void setAge(int age) {
        if (age <= 0 ) {
            throw new IllegalArgumentException("年龄需要大于0");
        }
        this.age = age;
    }

    public static void main(String[] args) {

        // 测试类应单独一个文件,且以Test为后缀
        Package aPackage = new Package();
        aPackage.setAge(-1);
    }
}

费氏数列

/**
 * 费氏数列
 * 1 1 2 3 5 8 13...
 *
 * @author timevaeless
 * @version 1.0
 * @date 2020/8/10 11:11 PM
 */
public class Fibonacci {

    /**
     * 递归:需要将方法不断的压栈出栈,非常消耗性能
     *
     * @param n
     * @return
     */
    public static int f(int n) {

        // ①递归的退出条件
        if (n == 1 || n == 2) {
            return 1;
        }
        return f(n - 1) + f(n - 2);
    }


    /**
     * 递推
     *
     * @param n
     * @return
     */
    public static int f2(int n) {

        int a = 1;
        int b = 1;
        int ret = 1;

        for (int i = 2; i < n; i++) {
            ret = a + b;
            a = b;
            b = ret;
        }

        return ret;
    }

    public static void main(String[] args) {

        System.out.println(Fibonacci.f2(6));
    }
}

JavaBean

JavaBean是一种Java语言写成的可重用组件,其他Java类可以通过反射机制发现和操作这些JavaBean的属性。

  • 类是公共的

  • 有一个无参的公共构造方法

  • 有属性,且有对应的get和set方法

static

使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关。

①静态成员方法中只能访问静态成员不能访问非静态成员。

②在非静态成员方法中既能访问非静态的成员又能访问静态的成员。

构造块和静态代码块

  • 每创建一个对象都会执行一次构造块。

  • 静态代码块随着类加载时只执行一次。

  • 先执行父类的静态代码块,再执行子类的静态代码块。

  • 执行父类的构造块,执行父类的构造方法体。

  • 执行子类的构造块,执行子类的构造方法体。

Singleton

  • 私有化构造方法,使用private关键字修饰。

  • 声明本类类型的引用指向本类类型的对象,并使用private static关键字共

同修饰

  • 提供公有的get方法负责将对象返回出去,并使用public static关键字共同

修饰。

  • 单例设计模式的实现方式有两种:饿汉式 和 懒汉式,在以后的开发中推

荐饿汉式(即在类中就初始化)。

/**
 * 单例模式
 *
 * @author timevaeless
 * @version 1.0
 * @date 2020/8/11 8:16 AM
 */
class Singleton {
    // 饿汉式, 多线程安全
    /*①*/private static volatile Singleton instance = new Singleton();

    /*②*/private Singleton() {}

    /*③*/public static Singleton getInstance() {
        return instance;
    }
}


class Singleton2 {
    // 懒汉式
    /*①*/private static volatile Singleton2 instance;

    /*②*/private Singleton2() {}

    /*③*/public static Singleton2 getInstance() {
        // 双重检测
        if (instance == null) {
            synchronized (Singleton2.class) {
                if (instance == null) {
                    instance = new Singleton2();
                }
            }
        }

        return instance;
    }
}

继承

  • 子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承

只是不能直接访问,但可以通过调用父类的get方法访问。

  • 无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法,

来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代

码super()的效果。

  • 子类和父类必须是 is a关系。
public class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("子类构造方法里没有super,就默认执行我!");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
  
public class Teacher extends Person {
    private int salary;

    public Teacher(String name, int age, int salary) {
        // super(); // 编译器隐式调用
        super(name, age); // 显示调用就不会走Person默认构造方法了
        // setName(name);
        // setAge(age);
        setSalary(salary);
    }

重写

public void show() {
  System.out.println("Person");
}

@Override
public void show() {
    super.show();
    System.out.println("Teacher");
}

访问控制符

package

  • 定义类时需要指定类的名称,但如果仅仅将类名作为类的唯一标识,则不可避的出现命名冲突的问题。这会给组件复用以及团队间的合作造成很大的麻烦!

  • 在Java语言中,用包(package)的概念来解决命名冲突的问题。

  • 在定义一个类时,除了定义类的名称一般还要指定一个包名,格式如下:

    ​ package 包名;

    ​ package 包名1.包名2.包名3...包名n;

  • 为了实现项目管理、解决命名冲突以及权限控制的效果。

  • 如果各个公司或开发组织的程序员都随心所欲的命名包名的话,仍然不能从根本上解决命名冲突的问题。因此,在指定包名的时候应该按照一定的规范。

    ​ org.apache.commons.lang.StringUtil

    ​ common 表示项目的名称信息;lang 表示模块的名称信息。

final

  • final本意为"最终的、不可改变的",可以修饰类、成员方法以及成员变量。

  • final关键字修饰类体现在该类不能被继承。主要用于防止滥用继承,如:java.lang.String类等。

  • final关键字修饰成员方法体现在该方法不能被重写但可以被继承。主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。

  • final关键字修饰成员变量体现在该变量必须初始化且不能改变。主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等。

常量

  • 在以后的开发中很少单独使用final关键字来修饰成员变量,通常使用public static final关键字共同修饰成员变量来表达常量的含义,常量的命名规范要求是所有字母都要大写,不同的单词之间采用下划线连。

多态

  • 多态主要指同一种事物表现出来的多种形态。

    饮料:可乐、雪碧、红牛、脉动、...

    宠物:猫、狗、鸟、小强、鱼、...

    人:学生、教师、工人、保安、...

    图形:矩形、圆形、梯形、三角形、…

  • 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)。

Shape rect = new Rect(2, 3, 4, 5);
// ①在编译阶段,rect会去找Shape中的show方法,如果Shape中没有show方法,则会编译报错
// ②运行阶段,rect会调用自己的show方法
rect.show();

// rect.getHigh(); // 父类不可以直接调用子类的方法
// rect.test(); // 静态方法编译或运行阶段均调用父类方法
// 多态的作用: 屏蔽了不同子类的差异性实现通用的编程带来不同的效果
Rect.draw(rect);

抽象

  • 抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。

  • 具体格式如下:

    ​ 访问权限 abstract 返回值类型 方法名(形参列表);

    ​ public abstract void cry();

  • 抽象类中可以有成员变量、构造方法、成员方法;

  • 抽象类中可以没有抽象方法,也可以有抽象方法;

  • 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有抽象方法并且使用abstract关键字修饰的类。

  • 抽象类之所以不能被实例化,就是因为有抽象方法功能没实现。

  • 抽象类的实际意义不在于创建对象而在于被继承。

  • 当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。

  • 既然抽象类不可以实例化,为什么还可以有构造方法?因为子类可以通过super()来调用抽象类的构造方法。

public abstract class AbstractAnimal {
    private String name;

    public AbstractAnimal(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract void eat();
}

public class Cat extends AbstractAnimal {

    // 抽象的意义:提供一个模板,让子类按照模板去写
    // 模板设计模式——抽象类对子类具有强制性和规范性

    public Cat(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println(getName() + " eat fish...");
    }

接口和抽象的区别

  • 定义抽象类的关键字是abstract class,而定义接口的关键字是interface。

  • 继承抽象类的关键字是extends,而实现接口的关键字是implements。

  • 继承抽象类支持单继承,而实现接口支持多实现。

  • 抽象类中可以有构造方法,而接口中不可以有构造方法。

  • 抽象类中可以有成员变量,而接口中只可以有常量。

  • 抽象类中可以有成员方法,而接口中只可以有抽象方法。

  • 抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需

    要重写(Java8以前的版本)。

  • 从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非

抽象方法需要使用default关键字修饰。

  • 从Java9开始增加新特性,接口中允许出现私有方法。
public interface Interface {

    // 私有方法,作用是给default方法调用
    private void a() {
        System.out.println("重用代码");
    }
    
    // 抽象方法
    public abstract void show();
    
    // default方法: 当新增一个方法时,不希望牵一发而动全身,则可以定义为default方法,可以被重写
    default int b() {
        a();
        return 0;
    }
    
    // 静态方法
    public static void c() {
        System.out.println("静态方法");
    }
}

回调

  • 回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的)。
  • 就是传递给这个方法一个类,这个方法回来执行我这个类中的方法。
public class InnerClass {

    public static void show(AbstractAnimal animal) {
        // 回调
        animal.eat();
    }

    public static void main(String[] args) {

        InnerClass.show(new AbstractAnimal("旺旺") {
            @Override
            public void eat() {
                System.out.println(getName() + "在吃屎");
            }
        });
    }
}

枚举

public class UserDefinedSeasonEnum {

    private final String desc; // 定义成final的意义是枚举必须不能改,而且要初始化

    public static final UserDefinedSeasonEnum SPRING = new UserDefinedSeasonEnum("春天");
    public static final UserDefinedSeasonEnum SUMMER = new UserDefinedSeasonEnum("夏天");
    public static final UserDefinedSeasonEnum FALL = new UserDefinedSeasonEnum("秋天");
    public static final UserDefinedSeasonEnum WINTER = new UserDefinedSeasonEnum("冬天");


    // 和单例类似,用户不能随便实例化
    private UserDefinedSeasonEnum(String desc) {
        this.desc = desc;
    }

    public String getDesc() {

        return desc;
    }
}

public enum SeasonEnum implements ISeason {

    SPRING("春天") {
        @Override
        public void show() {
            System.out.println("春天 " + this.name() + " " + getDesc());
        }
    },  // 等价于 private final static SeasonEnum SPRING = new SeasonEnum("春天")
    SUMMER("夏天"),  // 所以如果SeasonEnum没实现接口的方法,每个SeasonEnum都要实现
    FALL("秋天"),
    WINTER("冬天");

    private final String desc; // 定义成final的意义是不让setDesc

    SeasonEnum(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public void show() {
        System.out.println(this.name() + " " + getDesc());
    }
}

注解

  • 注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型。

  • 注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理。

  • 自定义注解自动继承java.lang.annotation.Annotation接口。

  • 通过@注解名称的方式可以修饰包、类、 成员方法、成员变量、构造方法、参数、局部变量的声明等。

  • 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

  • 如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型。

  • 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。

  • 元注解主要有 @Retention、@Documented、@Target、@Inherited、 @Repeatable。

  • @Retention 应用到一个注解上用于说明该注解的的生命周期,取值如下:

    ​ RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

    ​ RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中,默认方式。

    ​ RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

  • @Target用于指定被修饰的注解能用于哪些元素的修饰,取值如下:

  • @Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解。

  • @Repeatable表示自然可重复的含义,从Java8开始增加的新特性。

  • 从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:

    其中ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明

    语句中,如:泛型。

    其中ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

@Repeatable(ManTypes.class)
public @interface ManType {

    String value();
}
public @interface ManTypes {

    ManType[] value();
}

// @ManTypes({@ManType("超人"), @ManType("职工")}) // java8以前多个相同的注解方式
@ManType("超人")
@ManType("超人")
@ManType("超人")
@ManType("超人")
@ManType("超人")
public class AnnotationTest {
}