面向对象编程

什么是面向对象

  • 面向对象编程 (OOP, Object-Oriented Programming)
  • 面向对象的本质就是以类的方式组织代码,以对象的方式组织(封装)数据
  • 抽象
  • 三大特性
    • 封装
    • 继承
    • 多态

类与对象的关系

  • 类是一种抽象的数据类型,它是对某一类事物的整体描述/定义,但是并不能代表某一个具体的事物

  • 对象是抽象概念的具体实例

  • 认识论的角度是先有对象后有类。对象是具体的事物,类是对对象的抽象。

  • 代码运行角度是先有类后有对象。类是对象的模板。


创建与初始化对象

  • 使用new关键字创建对象

    创建对象除了会分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用


构造器详解

  • 类中的构造器也称为构造方法创建对象的时候必定会被调用

    • 使用new关键字,本质是在调用构造器

    • 一个类中即使什么内容都不写,它也会存在一个构造方法

  • 构造器的两个特点:

    1. 必须和类的名字相同
    2. 必须没有返回类型,也不能写void
  • 一旦定义了有参数的构造方法,无参构造方法必须显式定义才会有效


创建对象的内存分析

1
2
3
4
5
6
7
8
9
10
package com.hunter.oop;

public class Pet {
public String name;
public int age;

public void shout() {
System.out.println("叫了一声");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.hunter.oop;

public class Application {
public static void main(String[] args) {
Pet dog = new Pet();
dog.name = "旺财";
dog.age = 3;
dog.shout();

System.out.println(dog.name);
System.out.println(dog.age);

Pet cat = new Pet();
}
}

创建对象的内存示意图

Java内存分析


## 封装
  • 程序设计要追求高内聚、低耦合

    • 高内聚:尽可能类的每个方法只完成一件事
    • 低耦合:尽可能减少类的中的方法调用其他方法

    类的角度看:减少类内部对其他类的调用

    功能模块角度看:减少模块之间的交互复杂度

  • 封装

    封装就是对方法的实现细节进行隐藏。它是一种防止外界调用端去访问对象内部实现细节的手段。

  • 属性私有,提供公开接入的方法(getters/setters)

    代码更容易理解与维护,同时加强了代码的安全性。


继承

  • 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。

  • 子类是父类的扩展(extends)

    • 子类继承了父类,就会拥有父类的全部方法和属性(private除外)
    • final修饰的类无法被继承
  • 继承是类和类之间的一种关系,此外还有依赖、组合、聚合等

  • Object类

    在Java中,所有的类都默认直接或间接继承Object类

  • Java类只有单继承,没有多继承

    一个子类只能有一个父类

super

  • super();

    调用父类的构造器,必须要在子类构造器的第一行

    如果父类定义了有参数的构造方法,且没有显式定义无参构造方法,那么子类就不能定义无参构造因此定义有参构造方法之前,最好先显式定义无参构造。

  • super只能出现在子类的方法或构造方法中

  • 不能同时用superthis调用构造方法

    因为两者都要求自己在第一行,会冲突。

super和this的比较

  • this表示本身调用者这个对象

  • super表示父类对象,只能在继承后使用

  • 构造方法

    this():本类的构造方法,可以简化代码的编写

    super():父类的构造方法


重写 Override

方法的重载

重写需要有继承关系,是子类对父类非静态的方法进行重写,与属性无关。

  • 方法名必须相同

  • 参数列表必须相同

  • 修饰符:范围可以扩大,但不能缩小

    public > protected > default > private

  • 抛出的异常:范围可以缩小,但不能扩大

    Exception > ClassNotFoundException

  • 静态方法和非静态方法区别很大

    • 静态方法:方法的调用只和左边定义的数据类型有关(不会被重写)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      public class A {
      public static void test() {
      System.out.println("A.test()")
      }

      }


      public class B extends A {
      public static void test() {
      System.out.println("B.test()");
      }
      }


      public class Application {
      public static void main(String[] args) {

      B b = new B();
      b.test(); // 打印 B.test()

      A a = new B();
      a.test(); // 打印 A.test()
      }

      }
    • 非静态方法:重写

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      public class A {
      public void test() {
      System.out.println("A.test()")
      }

      }


      public class B extends A {
      public void test() {
      System.out.println("B.test()");
      }
      }


      public class Application {
      public static void main(String[] args) {

      B b = new B();
      b.test(); // 打印 B.test()

      A a = new B();
      a.test(); // 打印 B.test()
      }

      }
  • 为什么需要重写

    父类的功能子类不一定需要或不一定满足


多态

  • 多态就是同一方法可以根据发送对象的不同而采用多种不同的行为方式

    一个对象的实际类型是确定的,但指向的引用类型可以不确定

    1
    2
    3
    4
    5
    6
    7
    // Student extends Person
    Student s1 = new Student();
    Person s2 = new Student(); // 父类可以指向子类,但不能调用子类独有的方法
    Object s3 = new Student();

    s2.run(); // 如果子类重写了父类的方法,会执行子类的方法
    ((Student) s2).eat(); // 如果是子类有的,而父类没有的方法,必须进行强制类型转换
  • 一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多

  • 多态存在的条件

    1. 有继承关系
    2. 子类重写了父类方法
    3. 父类引用指向子类对象 Father f = new Son();
  • 多态是方法的多态,属性没有多态

    1. 静态方法(static)的调用只和左边定义的数据类型有关
    2. 常量(final)的调用也只和左边定义的数据类型有关
    3. private方法的调用也只和左边定义的数据类型有关

关键词 instanceof

关键词instanceof用于判断一个对象是什么类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Object > String
// Object > Person > Teacher
// Object > Person > Student
Object object = new Student();

System.out.println(object instanceof Student); // true
System.out.println(object instanceof Person); // true
System.out.println(object instanceof Object); // true
System.out.println(object instanceof Teacher); // false
System.out.println(object instanceof String); // false

Person person = new Student();
System.out.println(person instanceof Student); // true
System.out.println(person instanceof Person); // true
System.out.println(person instanceof Object); // true
System.out.println(person instanceof Teacher); // false
// System.out.println(person instanceof String); // 编译报错

System.out.println(X instanceof Y);语句能否编译通过要看X与Y之间是否有继承关系


引用类型的类型转换

1
2
3
Person obj = new Student();

((Student) obj).go() // 要使用【子类特有的方法】需要【强制类型转换】
  • 引用类型的类型转换可能会丢失自己本来的一些方法
    • 子类转换为父类是向上转型
    • 父类转换为子类为向下转型,属于强制转换
  • 引用类型的类型转换是为了方便方法的调用减少代码冗余

static关键字详解

代码块

1
2
3
4
5
6
7
8
9
10
publica class ClassName {
{
// 代码块(匿名代码块)
}

static {
// 静态代码块
}

}
  • 静态代码块

    加载类时就执行(只执行一次)

  • 匿名代码块

    • 创建对象自动创建,且在构造器之前创建
    • 运用场景:赋一些初始值

静态导入包

为了方便,想不加类名来使用工具类的静态方法,需要通过静态(static)导入包来实现:

1
2
3
4
5
6
7
import static java.lang.Math.random;

public class Application {
public static void main(String[] args) {
System.out.println(random());
}
}

抽象类

  • abstract修饰符可以修饰方法,也可以修饰类。

    如果修饰方法,方法就是抽象方法;如果修饰类,类就是抽象类。

  • 抽象类中可以没有抽象方法,但有抽象方法的类一定要声明为抽象类

    抽象类中可以有普通的方法

  • 抽象类不能用new关键字来创建对象,它是用来让子类继承的

    抽象类不能被创建对象,但有构造器那么构造方法的作用是什么

    抽象类的构造方法可以用来初始化抽象类内部声明的通用变量,并被各种实现使用。

  • 抽象方法

    只有方法的声明,没有方法的实现,实现交由子类来实现。

  • 子类继承抽象类,就必须要实现抽象类没有实现的抽象方法否则该子类也要声明为抽象类


接口

  • 普通类:只有具体实现

  • 抽象类:具体实现和**规范(抽象方法)**都有

  • 接口:只有规范,自己无法写方法。使约束和实现分离面向接口编程

    声明接口的关键字是interface

  • 接口就是规范,定义的是一组规则,让不同的人实现。

  • OO(Object-Oriented)的精髓就是对对象的抽象最能体现这一点的就是接口

    设计模式所研究的,实际上就是如何合理地去抽象

  • 接口中定义属性和方法

    属性常量 public static final但一般不会在接口中定义属性方法抽象public abstract。因此接口中定义的属性和方法的修饰符可以省略

  •   public interface UserService {
          
          // 接口中的所有定义的方法其实都是抽象的 public abstract,因此修饰符可省略
          void run();
      
      }
      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    	
    - **类只能单继承,但接口可以多继承(可以实现多个接口)**

    - 类通过关键字`implements`**实现接口**。**实现了接口的类,必须重写接口中的方法**。

    - **实现接口的类的命名**通常以`Impl`结尾。

    ```java

    // 类可以通过implements实现多个接口
    public class UserServiceImpl implements UserService, TimeService {

    @Override
    public void add(String name) {

    }

    @Override
    public void timer() {

    }
    }
  • 接口中没有构造方法(抽象类有构造方法)


内部类

  • 内部类就是在一个类的内部再定义一个类

成员内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Outer {

private int id = 10;
public void out() {
System.out.println("这是外部类的方法");
}

public class Inner {
public void in() {
System.out.println("这是内部类的方法");
}

// 获得外部类的私有属性
public void getID() {
System.out.println(id);
}
}
}


public class Application {
public static void main(String[] args) {

Outer outer = new Outer();

//通过外部类来实例化内部类
Outer.Inner inner = outer.new Inner();
inner.in();
inner.getID();

}
}

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Outer {

private int id = 10;
public void out() {
System.out.println("这是外部类的方法");
}

public static class Inner {
public void in() {
System.out.println("这是内部类的方法");
}

// 无法获得外部类的私有属性,因为静态类会先加载,非静态属性只有创建对象时才会生成;改成静态属性后,可以访问
/*
public void getID() {
System.out.println(id);
}
*/
}
}

局部内部类

局部内部类位于方法中

1
2
3
4
5
6
7
8
9
10
11
12
public class Outer {

public void method() {
// 局部内部类
class Inner {
public void in() {

}
}
}

}

匿名内部类

没有类的名称,必须借助接口或者父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
public static void main(String[] args) {
//没有名字初始化类,不用将实例保存到变量中
new Apple().eat();
}

// 变成了实现了接口的类(匿名内部类)
UserService userService = new UserService() {
@Override
public void hello() {

}
}
}

class Apple {
public void eat() {
System.out.println("1");
}
}

interface UserService {
void hello();
}

  • 一个Java类中可以有多个class类,但只能有一个public class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Outer {

    }


    // 可以作为测试类,对public class类进行测试
    class A {
    public static void main(String[] args) {

    }
    }

常用API

Object 类

概述

java.lang.Object类是Java语言中所有类的父类,其中描述的所有方法,子类都可以使用。如果一个类没有特别指定父类,则默认继承自Object类


toString 方法

直接打印对象的变量名,其实就是调用toString方法(对象在堆内存中的地址值)。但是Object默认的toString方法获取对象的地址值其实没有多少意义和可读性。

自定义的类,一般会重写toString方法,获取对象的属性


equals 方法

默认地址比较

Object默认的equals方法为:使用==运算符比较对象地址只要不是同一个对象,结果必然为false

对象内容比较

如果希望进行对象的内容比较,则可以重写equals方法。equals方法隐含着一个多态(传入的是Object类的对象)

1
public boolean equals(Object obj) {...}
  • 可选:
    • 添加一个判断,传递的参数obj如果是this本身,直接返回true,提高程序效率
    • 添加一个判断,传递的参数obj如果是null,直接返回false,提高程序效率
  • 添加一个判断,防止类型转换异常ClassCastException
  • 使用强制类型转换,把obj类转换成需要的类型
  • 再比较两个对象的属性
1
2
3
4
5
6
7
8
9
//idea自动生成的equals方法的重写
@override
public boolean equals(Object o) {
if (this == o) return true; // this本身
if (o == null || getClass() != o.getClass()) // o是null值 或 不能进行类型转换(反射技术,等效于 (o instanceof 当前对象的类型) )
return false;
当前类 x = (当前类) o; // 强制类型转换
return Objects.equals(this.strAttribute, o.strAttribute) && this.intAttribute == o.intAttribute; // strAttribute.equals(o.strAttribute) 是Object的equals方法,容易抛出空指针异常;因此采用Objects类的equals方法
}

clone 方法

clone方法可以实现对象的复制clone方法要求子类实现java.lang.Clonable接口,从而在子类中实现对象的复制。

浅克隆:对象在复制时仅复制基本类型的属性值到新对象中引用的变量不会被复制

深度克隆不仅复制基本类型的属性值到新对象中引用的变量本身也会被复制

实现深度克隆

要实现深度克隆,需要重写clone方法


Objects 类

JDK 1.7添加了一个java.util.Objects工具类,它提供了一些方法来操作对象,这些方法是null-save(空指针安全)的,用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象

在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题:

1
2
3
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.euqals(b));
}

日期时间类

Date 类

java.util.Date类表示特定的瞬间,精确到毫秒

  • 构造方法:
    • public Date()
    • public Date(long date):分配Date对象并初始化此对象,以表示自从标准基准时间(1970年1月1日00:00:00 GMT)以来的指定毫秒数

简单来说,使用无参构造,可以自动设置当前系统时间的毫秒时刻,指定long类型可以自定义毫秒时刻;输出Date类时,会自动转换为日期格式

  • 成员方法:

    long getTime():把当对象表示的时间转换为毫秒值


DateFormat类

java.text.DateFormat是日期/时间格式化子类的抽象类,可以通过这个类完成日期和文本之间的转换,即在Date对象和String对象之间来回转换。

  • 格式化

    按照指定的格式,从Date对象转换为String对象;String format(Date date)

  • 解析

    按照指定的格式,从String对象转换为Date对象;Date parse(String source)


构造方法

由于DateFormat为抽象类,因此需要常用的子类java.text.SimpleDateFormat。这个类需要指定格式化或解析的格式

构造方法为:public SimpleDateFormat(String pattern)

参数pattern是一个字符串,代表日期时间的自定义格式,常用的格式规则如下:

标识字母(区分大小写 含义
y
M
d
H
m
s

格式中的字母不能更改,但连接的符号可以改变

1
2
3
// 1. 创建SimpleDateFormat对象,构造方法中传递指定的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
  • 格式化

    调用SimpleDateFormat对象中的format方法,按照构造方法中指定的模式,把Date日期格式化为符合模式的字符串

    1
    2
    3
    Date date = new Date();
    String d = sdf.format(date);
    System.out.println(d);
  • 解析

    调用SimpleDateFormat对象中的parse方法,把符合构造方法中的格式的字符串解析为Date对象。

    ​ 注意:parse方法声明了一个ParseExceptoion,如果字符串和构造方法的格式不一样,就会抛出异常。

    1
    2
    Date date = sdf.parse("2021/07/31 22:26:33");
    System.out.println(date);

Calendar 类

java.util.Calendar在Date类之后出现,是一个抽象类,替换掉了很多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取

字段值 含义
YEAR
MONTH 月(0-11)
WEEK_OF_YEAR 一年中的第几周
WEEK_OF_MONTH 一个月中的第几周
DATE
DAY_OF_MONTH
DAY_OF_WEEK 一周中的第几天
DAY_OF_YEAR 一年中的第几天
HOUR
MINUTE
SECOND
MILLISECOND 毫秒

获取方法

Calendar类无法直接创建对象使用,它有一个静态方法getInstance(),该方法使用默认时区和语言环境返回Calendar类的子类对象


常用方法
  • public int get(int field):返回给定日历字段的值(参数使用Calendar类的静态成员变量
  • public void set(int field, int value):将给定的日历字段设置为给定值
  • public void set(int year, int month, int day):同时设置年月日
  • public abstract void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量
  • public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象
1
2
3
Calendar c = new Calendar.getInstance();
int year = c.get(Calender.YEAR);
System.out.println(year);

Math 类


BigInteger 类


BigDecimal 类


System 类

java.lang.system类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:

  • public static long currentTimeMills():返回以毫秒为单位的当前时间(和DATE类能达到相同的效果),可以用来测试程序的效率
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中
    • src 源数组
    • srcPos 起始位置
    • dest 目标数组
    • destPos 目标数组中的起始位置
    • length 要复制的数组长度

currentTimeMills 方法

1
2
3
4
5
6
long s = System.currentTimeMills();
for (int i= 0; i < 10000; i++) {
System.out.println(i);
}
long e = System.currentTimeMills();
System.out.println("程序共耗时:" + (e - s) + "毫秒");

arraycopy 方法

1
2
3
4
int[] src = {1, 2, 3, 4, 5};
int[] dest = {6, 7, 8, 9, 10};
System.arraycopy(src, 0, dest, 0, 3);
System.out.println(Arrays.toString(dest));

StringBuilder 类

又称为字符串缓冲区,可以提高字符串的操作效率(看作一个长度可变的字符串)。底层是一个初始长度为16的数组,但是没有被final修饰,因此长度可变。


构造方法

  • public StringBuilder():构造一个空的StringBuilder容器
  • public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去

常用方法

  • public StringBuilder append(...):添加任意类型数据,会将其内容转换为字符串形式,并返回当前对象自身,因此无需接收返回值

  • public String toString():将当前StringBuilder对象转换为String对象