Java面向对象

Java面向对象

Java中类被认为是一种自定义的数据类型,可以使用类来定义变量,所有使用类定义的变量都是引用变量

  • 这类变量将会引用到类的对象,对象由类负责创建。
  • 类用于描述看客观世界里某一类对象的共同特征。
  • Java使用类的构造器来创建对象。

构造器的作用

构造器用于对类实例进行初始化操作,构造器支持重载,如果多个重载的构造器里包含了相同的初始化代码,则可以把这些初始化代码放置在普通初始化块里完成,初始化块总在构造器执行之前被调用。

一、 类和对象

1. 定义类

语法:

[修饰符] class 类名
{
    零到多个构造器定义;
    零到多个属性;
    零到多个方法
}
  • Java类名必须是由一个或多个有意义的单词连缀成,每个单词首字母大写,其它字母小写,单词与单词之间不要使用任何分隔符
  • 类成员之间的定义顺序没有影响,各个成员之间可以相互调用
    • 注意:static修饰的成员不能访问没有static修饰的成员
  • 构造器是一个类创建对象的根本途径,没有构造器,这个类通常无法创建实例。
    • 若没有为类定义一个构造器,Java将自动为该类生成一个默认的构造器

属性语法格式说明:

  • 修饰符:修饰符可以省略。具体有
    • public
    • protected
    • private(与上述两个只能同时出现一个)
    • static
    • final
  • 属性类型:属性类型可以是Java语言允许的任何数据类型,包括基本类型和引用类型
  • 属性名:属性名第一个单词首字母小写,后面其它单词首字母大写,其它字母全部小写
  • 默认值:定义属性时可以设定默认值

方法定义

语法格式如下:

[修饰符] 方法返回值类型 方法名(形参列表)
{
    //由零条到多条可执行语句组成的方法体
}

方法语法格式说明:

  • 修饰符:可以省略
    • public
    • protected
    • private(与上述2个只能出现其一)
    • static
    • final
    • abstract(与final只能出现一个)
  • 方法返回值类型:返回值类型可以是Java语言允许的任何数据类型
    • 基本类型
    • 引用类型
    • 方法声明了返回类型,必须有一个有效的return语句,该语句返回一个变量或一个表达式
  • 方法名:与属性名的命名规则相同,建议以动词开头
  • 形参列表:形参列表用于定义该方法可以接受的参数

static关键字

static关键字修饰的成员表明它是属于这个方法共有的,而不是属于该类的单个实例,因为通常把static修饰的属性和方法称为类属性、类方法

构造器定义

语法格式如下:

[修饰符] 构造器名(形参列表)
{
    //由零条到多条可执行语句组成的构造器执行体
}
  • 修饰符:
    • public
    • protected
    • private
  • 构造器名:构造器必须和类同名
  • 形参列表:同方法定义

注意:构造器不能定义返回值类型声明,不能使用void定义构造器

2. 对象的产生和使用

创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器就可以创建这个类的实例:

//定义一个Person类型的变量
Person p;
//通过new关键字调用Person类的构造器,返回一个Person实例,将该Person实例赋给p
p = new Person();
=====================================
    //等价形式
Person p = new Person();

访问类

如果访问权限允许,类里定义的方法和属性都可以通过类或实例来调用。

  • 类或实例访问方法或属性:
    • 类.属性|方法
    • 实例.属性|方法
  • static修饰的方法和属性,既可以通过类来调用,也可以使用实例来调用
  • 没有static修饰的普通方法和属性,则只可以通过实例来调用。

3. 对象,引用和指针

对象变量的实质是指针:引用型变量存放的只是引用,指向实际的对象。

  • 真正的对象存储在堆内存中
  • 引用变量存储在栈内存中
  • 堆内存可以存在多个引用,多个引用指向同一个对象

4. 对象的this引用

this关键字是一个对象的默认引用,this关键字总是指向调用该方法的对象。

this出现位置不同时不同的作用:

  1. 构造器中引用该构造器执行初始化的对象
  2. 在方法中引用该方法的对象

其最大作用时让类中的一个方法,访问该类的另一个方法或属性。

二、 方法详解

方法时类或对象的行为特征的抽象,方法时类或对象最重要的组成部分。

1. 方法的所属性

在Java中,方法不能独立存在,必须属于类或对象。

  • 如果需要定义方法,只能在类中进行定义,不能独立定义一个方法。
  • 一旦方法使用了static修饰,则这个方法属于这个类,否则这个方法属于这个类的对像
  • 一旦一个类定义完成后,只要不再重新编译这个类文件,该类和该类的方法所拥有的方法时固定

注意

  • 同一个类的一个方法调用另外一个方法时,如果被调用方法时普通方法,则默认使用this作为调用者
  • 如果被调方法是静态方法,则默认使用类作为调用者。

Java方法所属性的体现

  1. 方法不能独立定义,方法只在类体里面定义
  2. 从逻辑意义来看,方法只能属于一个类或一个对象
  3. 永远不能独立执行方法,执行方法必须使用类或对象作为调用者

static修饰方法小结

  • 使用static修饰的方法属于该类,为该类的所有实例所共有
  • 使用static修饰的方法既可以使用类作为调用者来调用,也可以使用对象作为调用者来调用。
  • 不使用static修饰的方法属于该类的对象,不属于这个类。因此不使用static修饰的方法只能使用对象作为调用者调用,不能用类作为调用者调用。

2. 方法的参数传递机制

Java方法的参数传递方式是:值传递

  • 将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响

3. 形参长度可变的方法

Java允许定义形参长度可变的参数,从而允许为指定数量确定不确定的形参。

  • 语法是在定义方法时,在最后一个形参的类型后增加三点(...),则表明形参可以接收多个参数值,多个参数值被当成数组传入。

⭐使用数组作为形参

public static void test(int a , String[] books);

注意

长度可变的形参只能处于形参列表的最后,一个方法中最多只能包含一个长度可变的形参。

4. 递归

一个方法调用自己本身,就是递归。

  • 递归包含了一种隐式循环

5. 方法重载

Java允许同一个类里定义多个同名方法,只要形参列表不同即可。

Java程序中确定一个方法的三要素

  • 调用者:方法的所属者,既可以是类,也可以是对象
  • 方法名:方法的标识
  • 形参列表:当调用方法时,系统会更具传入的实参列表匹配

重载实例:

public class Overload {
    public void test()
    {
        System.out.println("no parameters");
    }
    public void test(String msg)
    {
        System.out.println("Overload the test method" + msg);
    }
    public static void main(String[] args)
    {
        Overload o1 = new Overload();
        o1.test();
        o1.test(" Hello ");
    }
    
}

三、 成员变量和局部变量

1. 成员变量和局部变量

在Java中的变量分类如下:

image-20210903004356321

成员变量

成员变量被分为类属性和实例属性两种,定义一个属性时不适用static修饰的就是实例属性,使用static修饰的就是类属性。

  • 类属性就是从类的准备截断就开始存在,直到系统完全销毁这个类。
  • 类属性的作用域与这个类的生存范围相同;实例属性则从这个类的实例创建开始起就存在,直到系统完全销毁这个实例,实例属性的作用域与对应实例的生存范围相同。

局部变量

  • 局部变量种类

    局部变量更具定义形式不同,分为下列三类:

    • 形参: 在定义方法签名时定义的变量,形参的作用域在整个方法内有效
    • 方法局部变量: 在方法体内定义的局部变量,作用域从定义该变量的地方生效,到该方法结束时失效
    • 代码块局部变量: 在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到代码块结束时失效

与成员变量对比

  1. 局部变量除形参外,必须显式初始化,先给方法局部变量和代码块局部变量指定初始值,否则不可以访问
  2. 在同一个类中,成员变量的作用范围时整个类有效,一个类不能定义两个同名的成员变量,也不能定义两个同名的局部变量

2. 成员变量的初始化和内存中的运行机制

当系统加载类或创建该类的实例是,系统自动为成员变量分配内存空间,并在分配内存空间后自动为成员变量指定初始值。

3. 局部变量的初始化和内存中的运行机制

  • 局部变量定义后,必须经过显示初始化才能使用,系统不会为局部变量执行初始化。
    • 直到程序为该变量赋值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
    • 局部变量不属于任何类或实例,总是保存在其所属方法的占内存中。
      • 如果局部变量时基本类型,直接把这个变量保存在该变量对应内存中;
      • 如果局部变量时引用类型,则该变量存放的时地址,通过该地址引用到该变量实际引用的对象或数组。
  • 栈内存中的变量无需系统垃圾回收,栈内存中的变量往往是随方法或代码块的运行结束而结束

4. 变量的使用归职责

定义一个成员变量时,成员变量将被放置到堆内存中,成员变量的作用域将扩大到类存在范围或对象存在范围,这种方法的缺点是:

  • 增大了变量的生存时间,导致更大的系统开销
  • 扩大了变量的作用域,不利于提高程序的内聚性

应该使用成员变量的情形

  • 如果需要定义的变量时用于描述某个类或某个对象的固有信息,这些变量应该定义为成员变量。如果描述的信息是与实例相关的,那应该定义为实例属性
  • 如果某个类中需要一个变量来保存该类或实例运行的状态信息,应该使用成员变量
  • 如果某个信息需要在某个类的多个方法之间进行共享,则这个信息应该使用成员变量来保存

注意:即使在程序中使用局部变量,应该尽可能的缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间越短,程序性能越好

  • 能使用代码块局部变量的地方,就坚决不要使用方法局部变量。

四、 隐藏和封装

在程序中如果通过某个对象直接访问其属性时,可能会引发潜在的翁提。因此需要封装技术。

1. 封装

封装(Encapsulation)是面向对象三大特征之一,指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对下个你内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

封装的目的

  • 隐藏类的实现细节
  • 让使用者只能通过实现预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性不合理的访问
  • 可以进行数据检测,从而有利于保证对象信息的完整性
  • 便于修改,提高代码的可维护性

实现封装的考虑

  1. 将对象的属性和实现细节隐藏,不允许外部直接访问
  2. 把方法暴露出来,让方法来操作或访问这些属性

2. 使用访问控制符

Java提供了三个访问控制符:private、protected和public,将访问权限分为三个级别,还有一个不加任何访问控制符的访问控制级别,提供了四个访问控制级别。

Java访问控制级别

访问级别由小到大为:private、default、protected、public

private

如果类中的一个成员使用private访问控制符来修饰,则这个成员只能在该类的内部被访问。

适用于修饰属性,使得属性隐藏在内部

default

如果类里的一个成员(属性、方法)或者一个顶级类不适用任何访问控制符修饰,就是默认访问控制

  • default访问控制的成员或顶级类可以被相同包下其他类访问。

protected

如果一个类成员使用protected访问控制符修饰,那么该成员既可以被同一个包中其它类访问,也可以被不同包中的子类访问

  • 通常情况下,使用protected修饰方法,是希望其子类来重写这个方法。

public

最宽松的访问控制级别,如果一个成员或一个顶级类使用了public修饰,这个成员或顶级类可以被所有类访问

访问控制级别表

private default protected public
同一个类中
同一个包中
子类中
全局范围

顶级类访问控制

顶级类只能由两种访问控制级别:public或default。

顶级类不能使用private和protected修饰,但顶级类既不处于任何类的内部,也就没有其它外部类的子类,因此private和protected无意义

  • 不适用任何访问控制修饰符的顶级类只能被同一个包中的所有类访问

访问控制符使用原则

  • 类的绝大部分属性都应该使用private修饰,除了一些static修饰的、类似全局变量的属性,才可以考虑使用public,除此之外,有些方法只是用于辅助实现该类的其它方法,这些方法被称为工具方法,工具方法也应该使用private。
  • 如果某个类主要用于做其它类的父类,该类里包含的大部分方法可能仅仅希望被赋予其子类进行重写,而不想被外界直接调用,应该使用protected修饰
  • 希望暴露出来给其它类自由调用的方法应该使用public修饰,因此,类的构造器通过使用public修饰,暴露给其它类中创建该类的对象。顶级类通常都希望被其它类自由使用,所以大多数顶级类都是用public修饰

3. package 和 import

Java引入包机制,是为类解决命名冲突,类文件管理问题。

Java允许将同一组功能相关的类放入同一个package下,从而组成逻辑上的类库单元。

将类放入一个指定的包结构下

具体语法,在文件的开头加入:

package packageName;

一旦源文件使用了这个package语句,则意味着该源文件里定义的所有类都属于这个包,位于包中的每个类的完整类名都应该是包名和类名的组合,如果其他人需要使用该类包下的类,就应该使用包名加类名的组合

  • 同一个包中的类不必位于相同的目录,但是需要在CLASSPATH添加
  • 应该把Java源文件放在与包名一致的目录下

image-20210903170631900

  • 包名必须在源文件中通过使用package语句指定,,class文件必须放在对应的路径之下
  • 一个源文件只能指定一个包,该源文件可以定义多个类,则这些将全部位于该包下

import关键字

import关键字可以向某个Java文件中导入指定包层次下某个类或全部类

  • 一个Java源文件可以包含多个import语句,多个import语句用于导入多个包层次下的类。

  • import *号只能用于导入类,不能用于包,需要具体包名

  • Java默认为所有源文件导入java.lang包下的所有类

Java常用包

  • java.lang:这个包下包含Java语言的核心类,无需使用import导入
  • java.util:这个包下包含了Java大量工具类、接口和集合框架类、接口
  • java.net:这个包下包含了一些Java网络编程相关的类/接口
  • java.io:这个包下包含了一些Java输入输出相关的接口
  • java.text:这个包下包含了一些Java格式化相关的类
  • java.sql:这个包下包含了Java进行JDBC数据库编程的相关类、接口
  • Java.awt:这个包下包含了抽下给你窗口工具集,用于图形用户界面程序
  • java.swing:这个包下包含了Swing图形用户界面编程相关的类或接口,可用于构建平台无关的GUI

五、 构造器

构造器是一个特殊的方法,这个特殊方法用于创建类的实例。

1. 构造器执行初始化

类并非完全由构造器所创建,程序员调用构造器时,系统会先为该对象分配内存空间,并为该对象执行默认初始化,这些操作都先于构造器完成。构造器只是使得一个不能被外部程序访问的对象通过this指针引用他,并作为构造器的返回值返回

  • 建议保留无参数的默认构造器

  • 构造器通常设置为public访问权限,从而允许系统中任何位置的类来创建该类的对象。除非在一些极端的情况下,需要限制创建该类的对象,可以设置成其它访问权限。

2. 构造器的重载

同一个类中可以包含多个构造器,多个构造器形参列表不同。这就是构造器的重载。

  • 构造器重载和方法重载基本相似:要求构造器的名字相同,但是形参不同

在一个构造器中调用另一个构造器初始化代码


public class Apple {
    public String name;
    public String color;
    public double weight;
    public Apple()
    {

    }
    public Apple(String name, String color)
    {
        this.name = name;
        this.color = color;
    }
    public Apple(String name, String color, double weight)
    {
        this(name, color);
        this.weight = weight;
    }
}

使用该方法能够符合软件工程:不要把相同的代码段书写两次以上的原则。便于软件的开发与维护

六、 类的继承

Java的继承具有单继承的特点,每个子类只有一个直接父类

1. 继承的特点

Java继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类,有的也称其为基类、超类。

  • 子类是一种特殊的父类,因此父类包含的范围总比子类包含的范围大,所以可以认为父类是大类,子类是小类。

Java继承语法

修饰符 class SubClass extends SuperClass
{
    //类的定义部分
}

Java的单继承

Java类只能有一个直接父类,但是实际上,Java可以拥有无限个多个间接父类

例如:

class Fruit extends Plants{}
class Apple extends Fruit{}

  • plants就是Apple的间接父类

如果定义一个Java类并未显示指定这个类的直接父类,则这个类默认拓展Java.lang.Object类。因此,java.lang.Object是所有类的父类,要么直接或简洁,所有Java对象都可以调用java.lang.Object类所定义的实例方法。

2. 重写父类的方法

子类拓展父类,子类是一个特殊的父类,大部分时,子类总是以父类为基础,额外增加新的属性和方法。当子类需要重写父类的方法时,可以重载父类的方法。

  • 子类与父类同名方法的现象被称为方法覆盖。

重写父类的规则

两同两小一大

  • 两同:方法名相同,形参列表相同
  • 两小:子类方法返回值类型应该比父类方法返回值类型更小或相等,子类方法声明抛出的异常还应该比父类方法声明抛出的异常更小或想等。
  • 一大:指的子类方法的访问权限比父类方法更大或相等
    • 覆盖方法和被覆盖方法要么是类方法,要么都是实例方法
  • 当子类覆盖了父类方法后,子类的对象无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法
    • 使用super或父类类名作为调用者来调用父类中被覆盖的方法
  • 如果父类方法具有private访问权限,则该方法对子类是隐藏的,因此其子类无法访问该方法,也无法重写该方法
    • 如果此时子类定义了一个与父类private方法具有相同方法名,相同他形参列表,相同返回值类型的方法,依然不是重写,只是在子类中定义了一个新方法

3. 父类实例的super引用

如需要在子类方法中调用父类被覆盖的实例方法,可以使用super作为调用者来调用父类被覆盖的实例方法。

语法如下:

public void callOverridedMethod()
{
    super.fly();
}

Java隐式创建父类对象

在创建某个类的对象时,系统会隐式创建该类父类的对象。只有一个子类对象存在,则一定存在与之相对的父类的对象。

  • 在子类方法中使用super引用时,super总是指向作为该方法调用者的子类对象所对应的父类对象

    • this总是指向调用该方法的对象
    • super总是指向this指向对象的父对象

注意

  • 如果被覆盖的是类属性,在子类的方法中则可以通过父类名作为调用者来访问被覆盖的类属性。
  • 如果子类里没有包含和父类同名的属性,则子类将可以继承到父类属性。如果在子类实例方法中访问该属性时,则五小组显式使用super或父类名作为调用者。
  • 如果在某个方法中访问名为a的属性,但没有显式指定调用者,系统查找a的顺序为:
    1. 查找该方法中是否有名为a的局部变量
    2. 查找当前类中是否包含名为a的属性
    3. 查找a的直接父类中是否包含名为a的属性,一次上溯a的父类,直到java.lang.Object类。如果最终找不到名为a的属性,则系统出现编译错误

4. 调用父类的构造器

子类不会获得父类的构造器,但有时候子类构造器里需要调用父类构造器的初始化代码。

子类调用父类构造器的实现

在一个构造其中调用另一个重载的构造器使用this调用实现,在子类中调用父类的构造器使用super调用来实现。

子类构造器调用父类构造器的情况

  1. 子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器
  2. 子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表调用本类另一个构造器。执行本类中另一个构造器时即会调用父类构造器
  3. 子类构造器执行体重既没有super调用,也没有this调用,系统会在执行子类构造器前,隐式调用父类无参数构造器

重载和重写

重载:重载发生在同一个类的同名方法之间,

  • 父类方法和子类方法之间也可能发生重载,因为子类会获得父类方法、

重写:重写发生在子类和父类的同名方法之间

七、 多态(Polymorphism)

1. 多态性

Java引用变量有两个类型:一个是编译时的类型,一个是运行时的类型,编译时的类型由声明变量时使用的类型决定,运行时的类型由实际赋给变量的对象决定。编译时类型和运行时类型不一致,就会出现多态(Polymorphism)

注意

引用变量在编译阶段智能调用其编译时类型所具有的方法,但是运行时则执行它运行时类型所具有的方法。

  • 引用变量只能调用声明该变量时所用类里包含的方法。

  • 通过引用变量来访问其包含的实例属性时,系统总是示图访问它编译时类所定义的属性,而不是它运行时类所定义的属性。

2. 引用变量的强制类型转换

引用变量智能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用对象确实包含该方法。

  • 如果需要让这个引用变量来调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转化需要借助类型转换运算符。
    • 类型转换符为()
    • 语法:(type)variable

强制类型转换注意:

  • 基本类型之间的转换智能在数值类型之间进行
    • 注意不能将数值类型与布尔类型进行转换
  • 引用类型之间的转换只能把一个父类变量转换成子类类型,
    • 如果两个没有任何继承关系的类不能进行类型转换
    • 试图把一个父类实例转换子类类型,则必须该对象为子类的实例

小结

当把子类对象赋给父类引用变量时,被称为向上转型(upcasting),这种转型可以实行

  • 把一个父类对象赋给子类引用变量时,需要进行强制类型转换
    • 使用instanceof运算符可以安全进行强制转换

3. instanceof运算符

instanceof运算符的前一个操作数通常时一个引用类型的变量,后一个操作数通常时一个类(可以是接口)

八、 继承与组合

继承是实现类重用的重要手段,但继承破坏封装。

组合也是实现类重用的重要方式,采用组合方式来实现类冲用能够提供更好的封装性。

1. 使用继承的注意点

因为子类在对父类继承时,子类可以直接访问父类的属性和方法,这会破坏父类的封装性,因为父类的实现细节对子类不再透明,可能会使得子类恶意窜改父类的方法。

父类设计原则

  1. 尽量隐藏父类的内部数据。尽量把父类的所有属性都设置为private访问类型,不用让子类直接访问父类属性
  2. 不要让子类可以随意访问、修改父类的方法。
    • 父类中仅为辅助其它方法的工具方法,应该使用private修饰,让子类无法访问该方法,
    • 如果父类中的方法需要被外部类调用,必须以public修饰,但又不希望子类重写方法,可以使用final'修饰
    • 如果希望父类的某个方法被子类重写,但不希望其它类自由访问,可以使用protected
  3. 不用在父类构造器中调用被子类重写方法

从父类派生子类的条件

  1. 子类需要额外增加属性,而不仅仅是改变属性值
  2. 子类需要增加自己独有的行为方式

2. 利用组合实现服用

复用一个类。除了把该类当成基类进行继承外,还可以把该类当成另一个类的组合成分,从而允许新类直接复用该类的public方法。

  • 继承和组合都允许新类中直接复用旧类的方法

组合

组合把旧类对象作为新类的属性进行其纳入,用以实现新类的功能,用户看到的是新类的方法,而不能看到嵌入对象的方法,因此在新类里使用private修饰嵌入旧类的对象。

九、 初始化块

Java使用构造器来对单个对象进行初始化块操作,使用构造器先把整个Java对象的状态初始化完成,然后将Java对象返回给程序,从而让Java对象的信息更加完整。

与构造器作用相似的初始化块,也对Java对象进行初始化操作。

1. 使用初始化块

初始化块是Java类里可以出现的第四种成员(属性、方法、构造器、初始化块),一个类里可以又多个初始化块,相同类型的初始化块之间有顺序:

  • 前面定义的初始化块先执行,后面定义的初始化块后执行

  • 语法如下:

[修饰符] {
    //初始化块的可执行代码
    。。。
}

实例

public class Person {
    //定义一个初始化块
    {
        int a = 6;
        //初始化块
        if (a > 4)
        {
            System.out.println("Person的初始化块:局部变量a的值大于4");
        }
        System.out.println("Person 的初始化块");
    }
    //定义第二个初始化块
    {
        System.out.println("Person的第二个初始化块");
    }
    //定义无参数的构造器
    public Person()
    {
        System.out.println("Person类的无参数构造器");
    }
    public static void main(String[] args)
    {
        new Person();
    }
}


输出如下:

⨯ Aloduin@DESKTOP-4DPJ151  F:\Java                         [15:42]  
❯ cd "f:\Java\" ; if ($?) { javac Person.java } ; if ($?) { java Person }
Person的初始化块:局部变量a的值大于4
Person 的初始化块
Person的第二个初始化块
Person类的无参数构造器

由输出可以看出:

  • 当创建对象时,系统总是先调用该类定义里定义的初始化块,如果一个类里定义了2个普通初始化块,则前面定义的初始化块先执行,后面定义的初始化块后执行
  • 初始化块无名字,没有标识,无法通过类、对象来调用初始化块
    • 初始化块值在创建Java对象时隐式执行,而且在执行构造器前执行
  • 初始化块的执行时隐式的,可以把多个普通的初始化块合并成一个初始化块
  • 普通初始化块、声明实例属性指定的默认值都可以认为是对象的初始化代码,它们执行顺序与源程序中排列顺序相同

初始化块与构造器差异

初始化块是构造器的补充,总是在构造器执行前执行,系统可以使用初始化块来进行对象的初始化操作。

与构造器不同点:

  • 初始化时一段固定执行的代码,不能接收任何参数
    • 初始化块对同一个类所有对象所进行的初始化操作完全相同。
  • 与构造器类似的,创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,系统会一直上溯到 java. lang.Object类,先执行 java. lang Object类的初始化块,开始执行 java. lang Object的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器……最后才执行该类的初始化块和构造器,返回该类的对象。

3. 静态初始化块

定义初始化块使用了static,则这个初始化块就被称为类初始化块、静态初始化块

  • 静态初始化块时类相关的,系统将在类初始化阶段执行静态初始化块

  • 静态初始化块比普通初始化块先执行

  • 静态初始化块通常用于对整个类进行初始化处理,通常用于对类属性执行初始化处理,不能对实例属性进行初始化处理

注意:

静态初始化块被称为类初始化块,属于类静态成员,同样遵循静态成员不能访问非静态成员的规则,因此静态初始化块不能访问非静态成员,不能访问实例属性和方法

十、 基本数据类型的包装类

Java包含8种基本数据类型,8个基本数据类型不支持面向对象的编程机制,基本数据类型的数据不具备对象属性,没有属性、方法可以调用。

Java可以使用包装类(Wrapper Class)为8个基本数据类型定义了相应的引用类型,称之为基本数据类型的包装类

基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
  • 把基本数据类型包装成包装类实例是通过对应包装类的构造器来实现的,8个包装类除了Character,还可以通过传入一个字符串参数来构建包装类对象

把基本类型转换成对应包装类对象


public class Primitive2Wrapper {
    public static void main(String[] args) {
        boolean b1 = true;
        Boolean b1Obj = new Boolean(b1);
        int it = 5;
        //通过构造器把it基本类型变量包装成包装类对象
        Integer itObj = new Integer(it);
        //把一个字符串转换为Float对象
        Float f1 = new Float("4.56");
        Boolean bObj = new Boolean("false");
    }
}

从包装类获取对应的基本类型

//取出Boolean对象里的boolean变量
boolean bb = bObj.booleanValue();
//去除Integer对象里的int变量
int i = itObj.intValue();
//取出Float对象里的float变量
float f = f1.floatValue();

基本类型变量和包装类对象之间的转换关系

graph LR a("基本类型变量")--"通过new WrapperClass(primitive)"-->b("包装类对象") b--"通过WrapperInstance xxxValue方法"-->a

自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)

  • 自动装箱:可以把一个基本类型变量直接赋值给对应的包装类变量,或赋值给Object变量
  • 自动拆箱:允许直接把包装类对象直接赋值给一个对应的基本类型变量

public class AutoBoxingUnboxing {
    public static void main(String[] args)
    {
        //直接把一个基本类型变量赋给Integer变量
        Integer inObj = 5;
        //直接把一个boolean类型变量赋给一个object类型变量
        Object boolObj = true;
        //直接把一个Integer对象赋给int类型变量
        int it = inObj;
        if ((boolObj) instanceof Boolean)
        {
            //先把object对象强制类型转换为Boolean类型,再赋值给boolean变量
            boolean b = (Boolean)boolObj;
            System.out.println(b);
        }
    }
}

包装类实现基本类型和字符串之间的转换

除了Character之外的所有包装类都提供了一个parseXxx(String s)静态方法。用于将一个特定字符串转换成基本类型比那辆

  • String类种提供了多个重载valueOf()方法,用于将基本类型变量转换成字符串
public class Primitive2String {
    public static void main(String[] args)
    {
        String inStr = "123";
        //把一个特定字符串转换为int变量
        int it = Integer.parseInt(inStr);
        System.out.println(it);
        String floatStr = "4.56";
        //把一个特定字符串转换为float变量
        float ft = Float.parseFloat(floatStr);
        System.out.println(ft);
        //把一个float变量转换成String变量
        String ftStr = String.valueOf(2.345f);
        System.out.println(ftStr);
        //把一个double变量转换为String
        String dbStr = String.valueOf(3.344);
        System.out.println(dbStr);
        //把一个double变量转换成String变量
        String dbstr = String.valueOf(3.344);
        System.out.println(dbStr);
        //把一个boolean变量转换为String变量
        String boolStr = String.valueOf(true);
        System.out.println(boolStr.toUpperCase());
    }    
}
  • 如果希望把基本类型转换为字符串,还有一种更简单的方法:

    • 将基本类型变量和“”进行连接运算,系统会自动把基本类型转换为字符串
    //intStr的值为5
    String intStr = 5 + "";
    

十一、 处理对象

Java对象都是Object类的实例,都可以直接调用该类中定义的方法,这些方法提供了处理Java对象的通用方法。

1. 打印对象和toString方法

  • 所有Java对象都可以和字符串进行连接运算,当Java对象和字符串进行连接运算时,系统自动调用Java对象toString方法的返回值和字符串进行连接运算
//下面两条语句等价
String pStr = p + "";
String pStr = p.toString() + "";
  • toString方法总是返回该对象实现类的类名+@+hashCode值,这个返回值不能真正实现自我描述功能
    • 若要实现类的自我描述,必须重构toString方法

重构toString方法实现类自我描述

public class TestToString {
    private String color;
    private double weight;
    public TestToString()
    {

    }   
    public TestToString(String color, double weight)
    {
        this.color = color;
        this.weight = weight;
    } 
    //重写toString方法,用于实现对象的自我描述
    public String toSring()
    {
        return "一个苹果,颜色时: " + color + ",重量是: "+ weight;
    }
}

2. ==和equals比较运算符

Java程序中测试两个变量是否相等有两种方式:

  • 利用==运算符

    • 如果两个变量是基本类型的变量,都是数值型,则只要两个变量的值相等,使用==判断九将返回true
    • 对于两个引用类型的变量必须指向同一个对象式,==判断才会返回ture
  • 利用equals方法

    • equals方法对于相等的判定,只需要引用字符串对象里包含的字符序列相同既可以认为式相等

    • equals方法式Object类提供的一个实例方法,因此所有引用变量都可以调用该方法来判断是否与其他引用变量相等

重写equals方法应该满足的条件

  • 自反性: 对任意x,x.equals(x)一定返回true
  • 对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)返回true
  • 传递性:对任意x,y,z,如果有x.equals(y),返回true,y.equals(z)返回true,则x.equals(z)一定返回true
  • 一致性:对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致
  • 对任何不是null的x,x.equals(null)一定返回false

十二、 类成员

1. 类成员定义

Java类中能包含属性、方法、构造器、初始化块、内部类和枚举类六种。其中能偶使用static修饰的属性、方法、初始化块,内部类,枚举类就是类成员。

  • 类成员属于整个类,当系统第一次准备使用该类时,系统会为该类属性分配内存空间,类属性开始生效,直到该类被卸载,该类的类属性所占有的内存才被系统的垃圾回收机制回收。

  • 类属性可以通过类来访问,也可以通过类的对象来访问。但通过类的对象来访问类属性时,实际上并不是访问该对象所具有的属性

    • 对象不包括类对应的类属性
  • 所有对象不保持类属性,类属性是由该类来保持的,同一个类的所有对象访问类属性时,实际访问的时该类所持有的属性。

  • 类方法,是类成员,类方法属于类,通常直接使用类作为调用者来调用类方法,也可以使用对象来调用类方法。

2. 单例(Singleton)类

一个类始终只能创建一个实例的类被称为单例类。

  • 将类构造器使用private修饰,可以把该类的所有构造器隐藏
  • 但是需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static来修饰
  • 该类必须缓存已经创建的对象,否则该类无法直到是否曾经创建过对象,也就无法保证只创建一个对象。

创建单例类实例

class Singleton {
    //使用一个变量来缓存曾经创建的实例
    private static Singleton instance;
    //将构造器使用private修饰,隐藏该构造器
    private Singleton(){}
    //提供一个静态方法,用于返回Singleton实例
    //该方法可以加入自定义的控制,保证只产生一个Singleton对象
    public static Singleton getInstance()
    {
        //如果instance为null,表明还不曾创建Singleton对象
        //如果instance不为null,则表明,已经创建了Singleton对象,将不会执行该方法
        if (instance == null)
        {
            //创建一个Singleton对象,并将其缓存起来
            instance = new Singleton();
        }

        return instance;
    }    
}
public class TestSingleton
{
    public static void main(String[] args)
    {
        //创建Singleton对象不能通过构造器,只能通过getInstance方法
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        //输出true
        System.out.println(s1 == s2);
    }
}

  • 通过上面的getInstace方法提供的自定义控制,保证Singleton类只能产生一个实例。所以再TestSingleton类的main方法中看到两次产生的Singleton对象实际上是同一个对象

十三、 final修饰符

final关键字可以用于修饰类、变量和方法。

  • 使用final修饰的类、方法、变量不可改变。

1. final变量

final修饰变量时,表示该变量一旦获得了初始值后就不可改变

  • final既可以修饰成员变量,又可以修饰局部变量,形参
  • final修饰的变量可以被赋予初始值,但是不能再次赋值

final修饰成员变量

当执行静态初始化块时可以对类属性赋初始值,当执行普通初始化、构造器时可以对实例属性赋处置否则由系统初始化。

对于使用final修饰的成员变量而言,一旦有了初始值后,就不能再被重新赋值,因此不可以在普通方法中对成员变量重新赋值。成员变量只能在定义该成员变量时指定默认值,或者在静态初始化块、初始化块和构造器为成员变量指定初始值。

  • 如果既没有定义成员变量时指定初始值,也没有在初始化块、构造器中卫成员变量指定初始值,那么这些成员变量的值一直时0、"\u0000"、false或null

使用final修饰成员变量注意点

当使用final修饰成员变量时,要么在定义成员变量时指定初始值,要么在初始化块、构造器中为成员变量赋初始值。

  • 如果定义该变量成员时指定了默认值,则不能在初始化块、构造器中为该属性重新赋值

使用final修改类属性、实例属性总结

  • 类属性:可以在静态初始化块中、声明该属性时指定初始值
  • 实例属性:可以在非静态初始化块、声明该属性、构造器中指定初始值

final修饰局部变量

系统不会对局部变量进行初始化,局部变量必须由程序显式初始化。

  • 对局部变量使用final修饰,可以选择是否指定默认值
  • 如果修饰的局部变量在定义是没有指定默认值,则可以在后面代码中进行对该final变量赋初始值,但只能一次,不能重复赋值
  • 已指定默认值,后续不可赋值

final修饰基本类型和引用类型的区别

当使用final修饰基本类型变量,不能对基本类型变量重新赋值,因此基本类型变量不能被改变,但是对于引用类型的变量而言,它保存的仅仅是一个引用,final只能保证这个引用引用的地址不变

  • 即可以一直引用一个对象,但这个对象可以完全发生改变

注意

如果final修饰的变量是基本数据类型,且在编译时就可以确定该变量的值,于是可以把该变量当成常量处理

  • Java对常量的命名规则是:常量名由多个有意义的单词连缀而成,每个单词的所有字母都大写,单词与单词之间使用下划线来分割

2. final方法

final修饰方法不可被重写!
对于一个 private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法——如果子类中定义一个与父类 private方法有相同方法名、相同形参列表、相同返回值类型的方法也不是方法重写,只是重新定义了一个新方法。因此,即使使用 final修饰一个 private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。

3. final类

final修饰的类不可以有子类

  • 可以使用final关键字来保证类不会被继承

4. 不可变类(immutable)

创建类的实例后,该实例的属性不可改变,这种类就是不可变类。

创建不可变类的规则

  • 使用private和final修饰符来修饰该类的属性
  • 提供带参数构造器,用于根据传入参数来初始化类里的属性
  • 仅为该类的属性提供getter方法,不要为该类的属性提供setter方法,因为普通方法无法修改final修饰属性
  • 如果没有必要,重写Object类中hashCode和equals方法。在equals方法更具关键属性来作为两个对象相等的标准,除此之外,还应该保证两个用equals方法判断相等的对象的hashCode也相等

与可变类相比,不可变量类对象在整个生命周期中永远处于初始化状态,它的属性不可改变。因此对不可变类的实例的控制将更加简单。
前面介绍fnal关键字时提到,当使用 final修饰引用类型的变量时,仅表示这个引用类型的变量不可被重新赋值,但引用类型变量所指向的对象依然可改变。这就产生了一个问题:当创建不可变类时,如果它包含属性的类型是可变的,那么其对象的属性值依然是可改变的—那么这个不可变类其实是失败的。

5. 缓存实例的不可变类

不可变类的实例状态不可以变,可以方便为多个对象共享。

  • 如果程序经常需要使用相同的不可变类实例,可以考虑缓存这种不可变类的实例。

十四、 抽象类

所谓抽象方法是指只有方法签名,没有方法实现的方法

1. 抽象方法和抽象类

抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类可以没有抽象方法

抽象方法和抽象类的规则

  • 抽象类必须使用 abstract修饰符来修饰,抽象方法也必须使用 abstract修饰符来修饰,抽象方法不能有方法体抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例。

  • 抽象类可以包含属性、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类、枚举类六种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。

  • 含有抽象方法的类(包括直接定义了一个抽象方法:继承了一个抽象父类,但没有完全实现父类包含的抽象方法;以及实现了一个接口,但没有完全实现接口包含的抽象方法三种情况只能被定义成抽象类。

  • 注意:抽象类同样包含和普通类相同的成员,只是抽象类不能用于创建实例,抽象类还能有抽象方法

posted @ 2021-09-05 22:36  Aloduin  阅读(104)  评论(0)    收藏  举报