Java基础笔记五 面向对象

為{幸葍}努か 提交于 2019-11-27 05:49:54

5. 面向对象

5.1. 面向对象的简介

面向过程

是一种看待问题、解决问题的思维方式。着眼点在于,问题是怎样一步步的解决的,然后亲力亲为的去解决这个问题。

面向对象

是一种看待问题、解决问题的思维方式。着眼点在于,找到一个能够帮助解决问题的实体,然后委托这个实体帮助解决问题。

Java是一种面向对象的语言。

可以使用Java这门编程语言,更容易的写出具有面向对象编程思想的代码。

Java是一种面向对象的语言,因此用Java写的程序一定是面向对象的程序。

5.2. 类的设计与对象的实例化

对象: 可以帮助解决问题的实体,就是一个对象。

万物皆对象

: 是由若干个具有相同的特征和行为的对象组成的一个集合。

类和对象的关系: 类是对象的集合 , 对象是类的个体

备注: 在程序设计中,一定是现有的类,再有的对象。

5.2.1. 类的设计

语法

[访问权限修饰符] class 类名 {
    // 类体
    // 类的所有的对象共有的特征
    // 类的所有的对象共有的行为
}

语法说明:

1. 类名: 是一个标识符, 遵循大驼峰命名法。
2. 特征: 又叫属性, 在类中的体现是一个全局变量。
3. 行为: 在类中的体现,是一个方法。

类是一种自定义的数据类型

5.2.2. 对象的实例化

对象的实例化,需要使用到关键字new,也就是说,类是一种自定义的引用数据类型。

5.3. 构造方法

是一个方法,这个方法和普通方法相比,有不一样的地方。

  1. 构造方法没有除了访问权限修饰符以外的其他修饰符。
  2. 构造方法没有返回值。并不是说,返回值类型是void。而是不要写返回值类型部分。
  3. 构造方法的名字必须和类名相同。
  4. 构造方法不能被显式调用。构造方法是在实例化一个对象的时候被自动调用。

构造方法默认的提供原则

  1. 如果一个类中没有写任何的构造方法,此时,这个类中会包含一个系统自动提供的,public权限的无参的构造方法。
  2. 如果一个类中写构造方法了,此时,系统将不再自动提供任何的构造方法。

构造方法是可以重载的,如何区分调用重载的构造方法?

通过参数区分。

构造方法的实际使用场景

构造方法,作为一个对象生命周期中的第一个方法,一般会在构造方法中,对对象的某一些属性进行初始化的赋值

5.4. 成员访问

访问类中的成员(属性、方法)

5.4.1. 成员分类

类中的成员,大致可以分为两种:静态成员、非静态成员。

5.4.2. 非静态成员

没有使用关键字 static 修饰的成员,就是非静态成员。也叫作实例成员。

非静态的成员,是属于对象的。在访问的时候需要使用对象来访问。而对象也叫作一个类的实例(instance),因此非静态成员,也叫作–实例成员。

5.4.3. 静态成员

使用关键字 static 修饰的成员,就是静态成员。也叫作 类成员。

静态的成员是属于类的。在访问的时候,需要用类来访问。

其实,静态成员也可以用对象访问。只是不推荐。

5.4.5. 静态成员与非静态成员的内存分析
  1. 非静态属性:空间的开辟,是发生在对象的实例化的时候。

    非静态属性的空间,在堆空间中,随着对象的实例化开辟,随着对象的销毁而销毁。

  2. 静态属性:空间的开辟,是发生在类第一次被加载到jvm内存。(当程序第一次使用到这个类的时候)。

    静态属性的空间,是在静态空间中,随着类的加载被开辟,程序运行的过程中,不会销毁。常驻内存。

5.4.6. 总结
  1. 静态成员,需要用类来访问。
  2. 非静态成员,需要用对象来访问。
  3. 在非静态方法中,可以直接访问非静态成员和静态成员。
  4. 在静态方法中,只能直接访问静态成员。

5.5. this关键字

this在程序中,可以用在非静态的方法中,和构造方法中。

在一个类的方法中,允许出现参数的名字和属性的名字重复。并且这样是合法的。

因为属性的空间开辟在堆上,而参数作为局部变量,空间开辟在栈中。他们在不同的空间中。

在这个方法中,直接写变量的名字,使用的是 参数,而不是属性。

this表示对当前对象的引用

1. 如果用在一个非静态方法中,谁调用这个方法,this就代表谁。
2. 在构造方法中,this表示刚刚被实例化的对象。

在一个类中,访问当前类的属性和方法的时候,在某些情况下,this是可以省略的。

如果省略掉this之后,对程序没有任何的影响,没有任何的歧义,此时的this就是可以省略的。

杀手锏:如果不知道哪些this可以省略,哪些this不能省略。那么全部不要省略。

this()调用当前类中的其他构造方法

在构造方法中,可以使用this()调用当前类中的其他的构造方法,具体调用哪一个构造方法,由小括号中的实参区分

注意事项:

  1. 在构造方法中,使用this关键字调用其他的构造方法,这一句话必须写在构造方法中的第一行。也就是说,在构造方法调用的代码之前,不能有任何的语句,包括输出语句。
  2. 不要循环调用。

5.6. 封装

是面向对象三大特性之一【封装、继承、多态】

封装,是一个比较抽象的概念。可以将一个代码段、一个功能包装起来,方便维护、方便在其他的地方去使用这部分功能。

封装可以分为 广义的封装 和 狭义的封装。

广义的封装:将一个功能封装成一个方法、将一个大的功能集封装成一个模块、…

​ 将某些具有固定格式的数据,封装成一个类的对象…

狭义的封装:将一个类中的某些属性私有化起来,不让外界直接访问。同时提供对应的set和get方法。

某一些属性,如果直接让外界访问。外界对其赋的值可能不是我们想要的。可能这些值从语法讲,没有问题;但是从逻辑上讲,是不可以的。这种情况下,我们就需要将属性私有化起来,不让外界直接访问。

从JavaBean规范出发,所有的属性,都需要私有化。

5.6.1. 单例设计模式

设计模式:由前人总结的,用来解决特定问题的一种解题思路。

单例设计模式:在项目的任意模块、任意位置,某个类的对象只能是唯一的一个。

如果每次需要获取到的类的对象需要时同一个对象,此时,对象的获取不能使用new的方式。因为new代表了开辟一块新的空间。两次new出来的空间一定是不相同的。

实现单例设计模式的步骤

  1. 把类的构造方法私有化,杜绝从类外通过new的方式实例化对象的可能性。

  2. 给类添加一个方法,返回一个当前类的对象。

  3. 单例分为懒汉式单例 和 饿汉式单例

    1. 声明一个私有的、静态的、当前类的对象

    2. 饿汉式:直接在声明的同时,对这个静态属性进行实例化

      public class Chairman {
      	
      	private static Chairman Instance = new Chairman();
      	
      	private Chairman() {
      		System.out.println("一个新的Chairman对象被实例化了");
      	}
      	
      	public static Chairman getChairman() {
      		return Instance;
      	}
      }
      
    3. 懒汉式:声明好静态的当前类对象后,不去实例化。当第一次调用getChairman方法的时候实例化。

      public class Chairman {
      	
      	private static Chairman Instance;
      	
      	private Chairman() {
      		System.out.println("一个新的Chairman对象被实例化了");
      	}
      	
      	public static Chairman getChairman() {
      		if (Instance == null) {
      			Instance = new Chairman();
      		}
      		return Instance;
      	}
      }
      

5.7. 包 Package

包,起到了组织代码、组织文件。类似于文件夹。

package: 声明当前文件属于哪一个包,为了给编译后的.class文件找路径。

在程序中,如果需要在一个类中,使用到另外一个包里面的类,有两种方式可以实现:

  1. 使用类的全限定名:从这个类最外层的包开始,一层一层的往里面找。
  2. 导包:使用关键字 import 导入指定的包(类)。

5.8. 继承

是面向对象三大特性之一【封装、继承、多态】

5.8.1. 什么是继承

如果有多个相关联的类,具有相同的属性和方法,那么,可以将这些相同的部分提取出来,单独做一个类。

这个被提取出来的,具有公共部分的类,叫做 – 父类,基类,超类(Super Class)

被提取的具有相同的属性方法的类,叫做 – 子类,派生类

他们之间的关系,叫做 – 继承。

子类 继承自 父类

5.8.2. 继承的语法

extends:用来描述继承

class 子类类名 extends 父类类名 {}
5.8.3. 继承的特点
  1. Java是单继承的。一个类只能有一个父类,但是一个类可以有多个子类。

  2. 一个类在继承了父类的同时,还可以被其他的类继承。

  3. 子类可以访问父类中看得到的成员。

    所谓的"看得到的",跟成员的访问权限有关。

    父类可以将属性、方法继承给子类。

  4. 子类在拥有父类成员的基础之上,还可以有自己的成员,而且自己特有的成员,只能被自己和自己的子类访问。

5.8.4. 访问权限修饰符

访问权限:指的是某一个类、属性、方法 可以被访问的范围。

访问权限 修饰符 可以修饰 访问级别
公共权限 public 类、属性、方法 在当前项目的任意位置都可以访问
保护权限 protected 属性、方法 在当前包中,和跨包的子类中可以访问
包权限 - 类、属性、方法 在当前包中可以访问
私有权限 private 属性、方法 在当前类中可以访问
5.8.5. 继承中的构造方法

一个对象在实例化的时候,会自动的调用构造方法。

一个对象,在实例化,在堆上分配内存空间的时候,会先实例化父类部分。会优先给从父类继承的属性分配空间。在实例化父类部分的时候,会自动的调用父类中的无参构造方法。

因此,如果父类中没有无参构造,会对子类造成影响,导致子类对象无法正常实例化。

关键字super: 代表对父类对象的引用,类似于this关键字

但是,对于super,一般情况下,我们只在两种场景下使用:

  1. 在构造方法中,使用super(),调用父类中的构造方法。
  2. 在非静态方法中、构造方法中,使用super调用父类中的方法。

如何解决这个问题:

  1. 给父类添加一个无参构造。
  2. 在子类的构造方法中,手动使用super()调用父类中可见的构造方法。
5.8.6. 方法的重写

方法重写Override:

在子类中,对从父类继承到的方法进行重新实现。

重写,又叫做覆写。在子类中,用子类的实现方式覆盖掉父类的实现方式。

子类从父类中继承到某一个方法,将父类的实现方式抛弃,改成自己的实现方式。

@Override

是系统内置的一个注解,用于方法前。

作用:检测下面的一个方法,是不是一个重写的方法。

注意:这个注解只是起到一个验证的作用,并不意味着没有添加这个注解,就不是一个重写方法了。但是出于规范,一般情况下,重写的方法都需要添加一个@Override

重写对方法的返回值、访问权限的要求

  1. 重写的方法,访问权限要大于等于父类中方法的访问权限。

    public > protected > 包权限 > private

  2. 重写的方法,返回值类型要小于等于父类方法的返回值类型。

final关键字

可以修饰 含义
变量 表示值不可以发生改变,是一个常量
表示最终类,这个类无法被继承
方法 表示最终方法,这个方法无法被重写
5.8.7. Object类

Object是Java的根类,在Java中,所有的类都直接或者间接的继承自Object类。

Object类中的几个方法:

  • getClass

    • 获取用来描述当前类的Class对象,可以获取一个类型。
  • equals

    • 由于 == 比较,对于对象来说,只能比较地址。因此,在进行比较的时候,很多时候不符合逻辑需求。

    • equals就是一个用来比较两个对象的方法。默认实现,比较的是地址。如果需要自定义比较的规则,可以重写这个方法。

    • 注意事项:

      虽然我们可以在equals方法中,进行自定义的规则比较。但是,在制定比较规则的时候,还是要遵循一定的规范的。
      1. 如果obj是null,一定要返回false
      2. 如果this == obj,一定要返回true
      3. 如果两个对象的类型不同,一定要返回false
      4. a.equals(b)如果是true,则 b.equals(a) 也一定要是true
      5. a.equals(b) == true && b.equals(c) == true, 那么 a.equals(c) 一定也得是true
      
  • hashCode

    • 获取一个对象的在一个散列集合中的索引。(哈希表)(HashSet、HashMap)
  • toString

    • 可以通过重写这个方法,实现自定义的对象的字符串表示形式。

5.9. 多态

是面向对象三大特性之一【封装、继承、多态】

5.9.1. 对象的转型

和数据类型转换比较像。

父类的引用可以指向子类的对象。

  1. 向上转型
    1. 由子类类型转型为父类类型。
    2. 向上转型一定会成功,是一个隐式转换。
    3. 向上转型后的对象,将只能访问父类中公有的方法和属性。
  2. 向下转型
    1. 由父类类型转型为子类类型。
    2. 向下转型可能会失败,需要显式转换。
    3. 向下转型后的对象,将可以访问子类中特有的方法和属性。

向下转型失败:

如果在做向下转型的时候,这个对象正好是要转型的类型,则此时会转型成功。但是如果这个对象不是要转型的类型,此时会转型失败。如果转型失败,会出现 ClassCastException。

5.9.2. instanceof关键字

向下转型可能会失败,如果转型失败,就会出现类型转化异常 ClassCastException,因此,在做向下转型的时候,一定要判断要转型的对象是不是指定的类型。

可以使用关键字 instanceof 判断对象是否是要转型的类型。

对象 instanceof 类

判断一个对象是否是指定的类型,判断结果是一个boolean。

5.9.3. 多态

多态:向上转型后的对象,调用父类中的方法,最终的实现是子类的重写实现。

5.10. 抽象类

5.10.1. 抽象类和抽象方法
  1. 抽象方法:使用关键字abstract修饰的方法,叫做抽象方法
    1. 抽象方法,只有方法声明,没有方法实现。
    2. 抽象方法,只能被定义在抽象类中。不允许在一个非抽象类中定义抽象方法。
  2. 抽象类:使用关键字abstract修饰的类,叫做抽象类
    1. 抽象类除了可以包含以前所有的成员之外,还可以包含抽象方法。
    2. 抽象类不能实例化对象。
    3. 非抽象子类,在继承一个抽象父类的时候,必须实现父类中的抽象方法。(通过重写)
5.10.2. 抽象类的使用场景

结合抽象类和抽象方法的特点:抽象方法,必须写在抽象类中。

得出抽象类的使用场景:

可以通过抽象类和抽象方法,实现某些简单规则的制定

可以在抽象类中,定义若干抽象方法,让所有的非抽象子类必须实现。这些抽象方法,其实都是可以被当做规则。

5.10.2. 抽象类

可以使用抽象类,进行简单的规则制定。约束所有的子类。约束一个规范,让所有的子类都实现这个规范,方便对子类的统一管理。

但是,抽象类的规则制定,是通过继承实现的。而在Java中,继承是单继承,父类只能有一个。因此,如果使用抽象类进行规则的制定,可能会对一个类原有的继承结构造成影响。

另外,因为单继承的存在,一个类最多只能遵守一种规则约束。

为了让一个类可以遵守多个规则约束,引入接口的概念。

5.11. 接口

接口,也是一种自定义的引用数据类型,跟类比较相似。可以使用接口进行规则的指定,对所有的实现类进行统一的约束,使其具有相同的对外的方法和功能。

Java是单继承的。其实很多面向对象的语言,都是单继承的。很多语言在抛弃了多继承的同时,会使用其他的方法间接的实现多继承。而Java就是通过接口间接的实现多继承的。

5.11.1. 接口的定义

接口的定义,需要使用关键字 interface

接口中成员的定义:

  1. 可以写方法:
    1. 接口中的方法,默认都是用 public abstract 修饰的。
    2. 即便接口中的方法没有任何的修饰符,他们依然是 public abstract。
  2. 可以写属性:
    1. 接口中的属性,默认的修饰符是 public static final。
    2. 接口中的属性定义,必须同时赋初始值。
5.11.2. 接口的实现

接口不是类,不能实例化对象。接口写完之后,是要给某一个类去实现的。

类,在实现接口的时候,需要使用关键字 implements

  1. 接口的实现,对于继承没有影响。一个类可以在继承父类的同时,实现接口。
  2. 如果一个类既有继承,又有实现。在类的定义部分,先写继承,再写实现。
  3. 一个类可以实现多个接口,一个接口也可以被多个类实现。

如果一个类实现了一个接口。那么这个类被称为这个接口的 实现类

  1. 如果一个实现类实现了接口,那么这个实现类中可以 “继承” 到接口中的成员。

实现接口注意事项

  1. 如果接口方法,在父类中已经被实现了。子类是可以继承到这些方法的,因此,在子类中,可以不去重写实现
  2. 如果一个类实现的多个接口中,有相同的方法。实现类中只需要实现一次即可。

Java8的新特性

1. static

在Java8中,可以使用static修饰一个接口中的方法。接口中静态的方法,需要添加实现。

接口中的静态方法,只能用接口自己调用。

2. default

在Java8中,可以使用default修饰一个接口中的方法。default方法,也需要添加实现。

使用default修饰的方法,需要用接口的实现类对象来调用。

其实,default就是给接口方法添加一个默认的实现方式,实现类在实现这个接口的时候,可以重写这个方法,也可以不重写这个方法。

5.11.3. 接口的继承

接口之间是有继承的。并且接口之间的继承是多继承。

子接口可以继承到父接口中所有的方法。

5.11.4. 接口的多态

对象转型

接口的引用,可以指向实现类的对象。

  1. 实现类类型转型为接口类型,是一个向上转型。
  2. 接口类型转型为实现类类型,是一个向下转型。

多态

向上转型后的对象,去调用接口中的方法,最终的实现是实现类中的实现方式。

5.11.5. 接口在案例
5.11.6. 函数式接口

如果一个接口中,有且只有一个方法,是实现类必须要实现的。这样的接口就叫做函数式接口。

@FunctionalInterface 是一个用来修饰接口的注解。可以去验证一个接口是不是一个函数式接口。

5.12. 内部类

5.12.1. 成员内部类
5.12.2. 静态内部类
5.13.3. 局部内部类
5.13.4. 匿名内部类

匿名内部类,作为内部类部分的重点,也是必须要掌握的。

没有名字的内部类。

匿名内部类,一般是配合其他类一块使用的。表示这个类的子类。

实际应用中,匿名内部类,一般情况下,是配合抽象类或者接口使用的

5.13. lambda表达式

5.13.1. 什么是lambda表达式

lambda表达式是Java8的新特性之一。其实本质上来讲,lambda表达式是一个匿名函数。

实际应用中,lambda表达式一般是配合抽象类或者接口使用的。配合接口的使用场景更多。

可以使用lambda表达式对一个接口进行非常简洁的实现。

Lambda表达式可以简化接口的实现。但是并不是所有的接口都可以使用lambda表达式实现,lambda表达式对接口是有要求的。要求接口是一个函数式接口。

5.13.2. lambda表达式的基础语法

lambda表达式是对接口中的方法进行简单的实现,本质是一个匿名函数。

对于lambda表达式的语法部分,需要重点关注的是:方法的参数列表、方法体

关键的符号: -> lambda运算符,读作 “goes to”

也就是说,一个完整的lambda表达式,由三部分组成:参数列表、lambda运算符 和 方法体

其中,lambda运算符的作用是分隔参数列表方法体

使用lambda表达式来实现接口中的抽象方法,因此,在使用接口引用调用接口中的方法的时候,实际执行的是lambda表达式中的实现方式。

5.13.3. lambda表达式的语法进阶
  1. 方法体部分的精简:
    1. 如果一个lambda表达式的方法体中,只有一句代码,则大括号可以省略。
    2. 如果lambda表达式方法体中唯一的一句代码,是一个返回语句。在省略了大括号的同时,必须省略return。
  2. 参数部分精简:
    1. 可以省略掉参数的类型,因为参数的类型在接口的方法声明中已经明确了。
      1. 如果有多个参数,那么要么都省略,要么都不省略。不要出现有的省略了,有的没有省略。
    2. 如果参数列表中有且只有一个参数,则小括号可以省略。
5.13.4. 函数引用

如果一个lambda表达式的方法体中,只是在调用其他的方法。这种情况下,对于方法的参数、方法体、lambda运算符。都可以省略。直接引用这个要调用的方法即可。

方法引用,分为两部分:

第一部分:引用的方法调用方。

第二部分:引用方方法。

中间使用两个冒号分隔。

方法引用,其实就是用一个已经存在的方法来实现指定的接口中的抽象方法。因此:引用的方法参数和返回值必须和接口中的方法保持一致。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!