Java基础-面向对象

134 阅读11分钟

面向对象(Object Oriented Programming)的主线

  1. Java类及类的成员:属性,方法,构造器; 代码块、内部类
  2. 面向对象的三大特征:封装、继承、多态,(抽象性)
  3. 其他关键字:this,super,static,final,abstract,interface,package...

1 类

1.1 简介

类是Java应用程序中,必不可少的单位;创建类的关键字是:class;

1.2 预定义类

所谓预定义类,就是Java语言根据实际业务场景,帮我们预定义好的类。我们只需要掌握如何使用,不需要再去额外创建。常用的预定义类,例如:

  • Scanner :键盘输入
  • Math :数学相关类
  • String :字符串类
  • Date :时间日期相关类
  • List :集合类

1.3 内部类

内部类是指一个类在另外一个类的内部,是定义在另一个类中的类。

1.3.1 常规内部类

@Data
public class Tree {

    private String treeName;

    private String treeType;

    private List<Leaf> leafs;


    @Data
    public class Leaf {
        private String color;

        private String leafSize;

        public void show() {
            System.out.println("tree name: " + Tree.this.treeName);
            System.out.println("tree name: " + treeName);
        }
    }

    public static void main(String[] args) {
        Tree tree = new Tree();
        Leaf leaf = tree.new Leaf();
    }
}

上面就是一个内部类的例子,Leaf是Tree的内部类。

  • 内部类中可以直接访问外部类的数据,包括私有数据。
  • 常规内部类中的方法或者字段不能是静态的。

字节码分析

我们查看下内部类的字节码,如下图:

image.png

我们看到内部类Leaf的构造方法init,实际上会隐式地将外部对象传入,初始化,这样才能在内部类中访问。

1.3.2 局部内部类

局部内部类,比如只有在一个方法内部创建。

@Data
public class Tree {

    private String treeName;

    private String treeType;

    private List<Leaf> leafs;

    public void treeRoot(int height) {
        class TreeRoot {
            private int rootHeight;

            public void showRootHeight() {
                this.rootHeight = height;
                System.out.println("root height " + height);
            }
        }
        TreeRoot root = new TreeRoot();
        root.showRootHeight();
    }
}
  • 局部类不能用public或private访问说明符进行声明,因为作用域只在这个局部,没有必要。
  • 局部内部类可以完全将自己隐藏起来,体现良好的封装性。
  • 局部内部类可以直接访问方法的变量。

1.3.3 匿名内部类

匿名内部类,就更加简洁了,连类名都省略了, 这个配合lambda使用,非常简便。

public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable");
            }
        };

        Runnable runnable2 = () -> System.out.println("runnable");
    }

匿名内部类也体现了良好的封装性和简洁性。

1.3.4 静态内部类

这个也使用的非常频繁。java源码中也有很多这样的例子,比如HashMap中,Node节点就是一个静态内部类。

image.png

如果用static来修饰一个内部类,那么就是静态内部类。这个内部类属于外部类本身,但是不属于外部类的任何对象。因此使用static修饰的内部类称为静态内部类。静态内部类有如下规则:

  • 静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。
  • 外部类可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象访问其实例成员。

1.3.5 静态内部类与普通内部类

普通内部类:

  • 可以访问外部类的所有属性和方法

  • 普通内部类中不能包含静态的属性和方法 静态内部类:

  • 静态内部类只能访问外部类的静态属性及方法,无法访问外部类的普通成员(变量和方法)

  • 静态内部类可以包含静态的属性和方法

1.4 类图

image.png

具体的属性、方法、构造器等在下面介绍

2 对象

2.1 简介

  • 万物皆对象,所有的东西都是一个对象
  • 对象是类的具体实现,一个类可以创建任意多个对象
  • 每个对象其实都是一个副本,对象与对象之间互不干涉
  • 关键字: new
  • 使用:
    • 对象名.属性名 -->student.username
    • 对象名.方法名() --->student.eat();

2.2 匿名对象

我们也可以不定义对象的引用,而直接调用这个对象的方法。这样的对象叫做匿名对象, 如:

new Person().shout();
  • 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。

3 属性

属性主要是从局部变量与成员变量(属性)的角度介绍异同点:

相同点

  • 定义变量的格式:数据类型 变量名 = 变量值
  • 先声明后使用
  • 变量都有其对应的作用域

不同点

  • 在类中声明的位置不同:成员变量直接定义在类的{}内;局部变量声明在方法内、方法形参、代码块、构造器内部的变量
  • 权限修饰符不同: 在声明成员变量时,指明其权限,使用权限修饰符;局部变量不可以使用权限修饰符(最多可以使用final)。
  • 默认初始化值的情况: 成员变量根据其类型,都有默认初始化值。局部变量:没有默认初始化值。意味着我们在调用局部变量之前,一定要显式赋值。
  • 成员变量加载到堆空间中(static会放在方法区)局部变量:加载到栈空间

4 方法

4.1 方法的声明

权限修饰符 返回值类型 方法名(形参列表){方法体}

4.2 权限修饰符

image.png

4.3 方法重写

4.3.1 简介

方法重写(Override)是一种语言特性,它是多态的具体表现,它允许子类重新定义父类中已有的方法,且子类中的方法名和参数类型及个数都必须与父类保持一致,这就是方法重写。方法重写在 Java 中应用的场景有很多,其中比较经典的是 Object 类中的 equals 方法。

4.3.2 注意事项

  • 子类方法的权限控制符不能变小,也就是如果父类方法的权限控制符是 protected,那么子类的方法权限控制符只能是 protected 或 public;

  • 子类方法返回的类型只能变小,也就是说如果父类方法返回的是 Number 类型,那么子类方法只能返回 Number 类型或 Number 类的子类 Long 类型,而不能返回 Number 类型的父类类型 Object;

  • 子类抛出异常的类型只能变小;

4.4 方法重载

4.4.1 简介

方法重载是指在同一个类中,定义了多个同名方法,但同名方法的参数类型或参数个数不同就是方法重载。

4.4.2 注意事项

  • 第 1 匹配原则:优先匹配相同数据类型,方法重载会优先调用和方法参数类型一模一样的方法,比如只有一个 String 类型的参数调用,会优先匹配只有一个 String 参数类型的重载方法;

  • 第 2 匹配原则:如果是基本数据类型,会自动转换成更大的基本数据类型进行匹配,比如调用的参数是 int 类型,那么会优先调用基本类型 long,而非包装类型 Integer(这点需要特殊注意,容易出错)。

  • 第 3 匹配原则:自动装箱和自动拆箱匹配,参数调用也会进行自动拆箱和自动装箱的方法匹配,比如调用参数传递的是 int 类型,那么它可以匹配到 Integer 类型的重载方法;

  • 第 4 匹配原则:会按照继承路线依次向上匹配父类,如果匹配不到当前类,会尝试匹配它的父类,或者是父类的父类,依次往上匹配;

  • 第 5 匹配原则:可变参数匹配,如果方法是可选参数方法,那么它的调用优先级是最低的,在最后阶段才会匹配可选参数方法。

方法重载会按照以上的 5 个原则依次进行匹配,符合规则的方法会被优先调用。除了以上匹配原则之外,还需要特殊注意一点,不同的返回类型不能作为方法重载的依据,也就是不同的返回值类型不算方法重载

4.5 重写与重载

方法重写(Override)和方法重载(Overload)都是面向对象编程中,多态特性的不同体现。方法重写和方法重载的具体不同,主要体现在以下 6 个方面: image.png

5 构造器

5.1 简介

在 Java 中,可以通过编写构造器来确保每个对象的初始化。构造器的作用是用来建立一个新的类的实例,当一个对象被创建时,JVM 使用一个构造函数,并为其分配内存空间。构造函数的目的是初始化对象的状态,为所有声明的属性赋值。如果我们没有自定义构造函数,JVM 就会为这些属性分配默认值。

5.2 注意事项

  • 创建构造函数的两条规则是:构造函数的名称应与类相同。Java 构造函数不能有返回类型。
  • 构造函数不能被 abstractstatic 或 final 修饰,编译会报错
  • 构造函数可以重载但不能被覆盖

6 代码块

6.1 简介

使用{}定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可以分为以下三种:

  • 普通代码块
  • 构造(实例)代码块
  • 静态代码块

6.2 普通代码块

定义在方法中的代码块

public class TestDemo1 {
    public static void main(String[] args) {
        {//普通代码块直接用{}定义,
            int x =10;
            System.out.println("x1 = "+x);
        }
        int x = 200;
        System.out.println("x2 = "+x);
        {
            int y = 300;
            System.out.println("y = "+y);
        }
    }
}

6.3 构造(实例)代码块

构造代码块:定义在类中的代码块(不加修饰)。也叫:实例代码块。 构造代码块一般用于初始化实例成员变量。

public class Student {
    private String name;
    private String gender;
    private int age;
    private double score;

    // 构造函数
    public Student(){
        System.out.println("Student的构造函数被执行了!");
    }

    // 实例代码块
    {
        this.name = "Bonnie";
        this.age = 19;
        this.gender = "女";
        System.out.println("实例代码块初始化了!");
    }

    public void show(){
        System.out.println("name:"+name+"age:"+age+"gender:"+gender);
    }


    public static void main(String[] args) {
        Student s1 = new Student();
        s1.show();
    }
}

实例代码块一般都是优先于构造方法执行的。因为编译完成之后,编译器会将实例代码块中的代码拷贝到每个构造方法第一条语句前
我们从字节码角度来看,其实就是拷贝一下实例代码块到构造方法之前。
实例代码块一般用来初始化实例出的对象的普通的成员变量

6.4 静态代码块

使用static定义的代码块称为静态代码块。 一般用来初始化静态成员变量

public class Student {
    private String name;
    private String gender;
    private int age;
    private double score;
    private static String classRoom;

    // 构造函数
    public Student(){
        System.out.println("Student的构造函数被执行了!");
    }

    // 实例代码块
    {
        this.name = "Bonnie";
        this.age = 19;
        this.gender = "女";
        System.out.println("实例代码块初始化了!");
    }

    // 静态代码块
    static {
        classRoom = "三年二班";
        System.out.println("静态代码块被调用了!");
    }

    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student();
        System.out.println(Student.classRoom);
    }
}

静态代码块是优先于其他而执行的,而且整个过程中只执行一次。静态成员变量是类的属性,所以是在JVM加载类的时候开辟空间并同时初始化的。

7 三大特性

7.1 封装

7.1.1 简介

隐藏对象的属性和实现细节,仅对外公开访问方法.增强安全性和简化编程,使用者不必了解具体的实现细节,而只要通过对外公开的访问方法,来使用类的成员。

7.2 继承

Java 中类可分为以下三种:

  • 普通类:使用 class 定义且不含有抽象方法的类。
  • 抽象类:使用 abstract class 定义的类,它可以含有或不含有抽象方法。
  • 接口:使用 interface 定义的类。

上述三种类存在以下的继承规律:

  • 普通类可以继承(extends)普通类,可以继承(extends)抽象类,可以继承(implements)接口。
  • 抽象类可以继承(extends)普通类,可以继承(extends)抽象类,可以继承(implements)接口。
  • 接口只能继承(extends)接口

各继承规律中的约束:

  • 一个普通类或一个抽象类,要么继承一个普通类,要么继承一个抽象类,即所谓的单继承
  • 一个普通类或一个抽象类或一个接口,可以继承任意多个接口。
  • 一个普通类继承一个抽象类后,必须实现这个抽象类中定义的所有抽象(abstract)方法,否则就只能被定义为抽象类。
  • 一个普通类继承一个接口后,必须实现这个接口中定义的所有方法,否则就只能被定义为抽象类。
  • 抽象类继承抽象类,或者实现接口时,可以部分、全部或者完全不实现父类抽象类的抽象(abstract)方法或父类接口中定义的方法。

7.3 多态

继承是多态得以实现的基础。实现多态必须具体三个基本条件:

  1. 继承:在多态中必须存在有继承关系的子类和父类。
  2. 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  3. 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。 只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

QAQ 三大特性