对象的交响曲:深入理解Java面向对象的绝妙之处

简介: 对象的交响曲:深入理解Java面向对象的绝妙之处

类与对象

类( class )是构造对象的模板或蓝图,描述应用程序所对应的问题域中的对象。用 Java 编写的所有代码都位于某个类的内部,由类构造(construct)对象的过程称为创建类的实例 ( instance )。

面向对象的原则之一就是封装,封装是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。**对象中的数据称为实例域,操纵数据的过程称为方法。**对于每个特定的类实例(对象)都有一组特定的实例域值。实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。

同一个类的所有对象实例,由于支持相同的行为而具有家族式的相似性。对象的行为是用可调用的方法定义的。

类之间的常用关系

依赖(uses-a)

使用关系,如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类。

聚合(has-a)

包含关系,聚合关系意味着类 A 的对象包含类 B 的对象。

继承(is-a)

继承关系,如果类 A 扩展类 B , 类 A 不但包含从类 B 继承的方法, 还会

拥有一些额外的功能。

表达类关系的 UML 符号

局部变量

局部变量不会自动地初始化为 null,而必须通过调用 new 或将它们设置为 null 进行初始化

对象与对象变量

在对象与对象变量之间存在着一个重要的区别。对象变量只是一个引用,并没有实际包含一个对象,而仅仅引用一个对象。

在 Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new 操作符的返回值也是一个引用。所有的 Java 对象都存储在堆中。 当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。

构造器

在 Java 程序设计语言中, 使用构造器( constructor )构造新实例。

构造器是一种特殊的方法,用来构造并初始化对象,构造器的名字应该与类名相同。

构造器具有以下特点:

  1. 构造器与类名同名
  2. 每个类可以有一个以上的构造器
  3. 构造器可以有 0个、 1个或多个参数
  4. 构造器没有返回值
  5. 构造器伴随着 new 操作一起创建
  6. 无参构造器:很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会设置为适当的默认值。数值型数据设置为 0、布尔型数据设置为 false、所有对象变量将设置为 null。如果类中无构造器,则默认提供。如果类中声明了其他构造器,则需要显示声明无参构造。

方法

**方法用于操作对象以及存取它们的实例域。**方法可以访问所调用对象的私有数据。在实现一个类时,由于公有数据非常危险,所以应该将所有的数据域都设置为私有的。

私有方法

有些方法也是需要设置成私有,只需将关键字 public 改为 private 即可。

final 实例域

**可以将实例域定义为 final。构建对象时必须初始化这样的域。**也就是说,必须确保在每 一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。

final 修饰符大都应用于基本( primitive )类型域,或不可变( immutable )类的域。

如果类中的每个方法都不会改变其对象,这种类就是不可变的类。例如,String类就是一个不可变的类。

public class FinalObj {
    private final String name;
  
    public FinalObj(String name) {
        this.name = name;
    }
}

参数值传递

Java 是值传递,只是,对于基本数据类型,传递的是基本类型的变量值的拷贝。对于引用类型,传递的是该引用所指向的对象在内存中的地址值的拷贝,虽然对象是拷贝,但是拷贝后的对象和原对象的地址值是一样的,所以可以直接根据地址值修改对象的状态。

静态域和静态方法

静态域

如果将域定义为 static,每个类中只有一个这样的域。而每一个对象对于所有的实例域却都有自己的一份拷贝。

通过类型直接调用 User.id

静态常量
public class Math{
  public static final double PI = 3.14159265358979323846; 
} 

在程序中,可以采用 Math.PI 的形式获得这个常量。

如果关键字 static 被省略, PI 就变成了 Math 类的一个实例域。需要通过 Math 类的对象访问 PI,并且每一个 Math对象都有它自己的一份 PI 拷贝。

静态方法

通过类名直接调用,不需要由对象去访问。

在静态方法中,不可以使用 this 访问实例域,方法所需参数都是通过显式参数提供,但是可以访问自身的静态域(被static修饰)

注意,静态方法也是可以使用对象去访问,只是容易混淆,不够规范,所以没人这么用。

静态方法还有另外一种常见的用途。类似 LocalDate 和 NumberFormat 类使用静态工厂方法来构造对象。

main 方法

main 方法不对任何对象进行操作。 事实上,在启动程序时还没有任何一个对象。 静态的 main 方法将执行并创建程序所需要的对象。

main 方法是单个类程序运行的入口,一般可以用来写简单的单元测试。

显式参数和隐式参数

显式参数是明显地列在方法声明中的,静态方法中的参数就是显式参数。

关键字 this 表示隐式参数,成员方法中的参数是隐式参数。

方法重载

如果多个方法(比如, StringBuilder 构造器方法)有相同的名字、不同的参数,便产生了重载。

编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如果编译器找不到匹配的参数,就会产生编译时错误,因为根本不存在匹配, 或者没有一个比其他的更好。

方法名相同,参数列表、参数类型不同,并且与返回值无关。

初始化域变量

可以通过三种方式初始化域变量:构造器、变量直接赋值、静态代码块。

Java 允许使用包( package )将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

使用包的主要原因是确保类名的唯一性,要想将一个类放人包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。

package test.java;

如果没有指定包,那么会有一个默认的包,编译器编译之后生成的字节码文件也会生成在所声明的包下。

导包

一个类可以使用所属包中的所有类,以及其他包中的公有类( public class )。我们可以采用两种方式访问另一个包中的公有类。

类名前加完整包名
java.tiie.LocalDate today = java.tine.Local Date.now() ;
使用 import 语句:

import 语句可以只导入一个包下的一个公共类,还可以导入这个包下所有的公共类。

import 语句位于文件顶部,但是在 package 的后面。

import java.time.Local Date; 
import java.util .*;

* 只能位于 import 语句的最后面。

当类名冲突的时候,想要使用,只能使用第一种方式。

静态导入

import 语句不仅可以导入类,还增加了导入静态方法和静态域的功能。

import static java.lang.System.*; 

之后在本类就可以使用 System 类的静态方法和静态域,而不必加类名前缀。

包作用域

  • 标记为 public 的部分可以被任意的类使用
  • 标记为 private 的部分只能被定义它们的类使用
  • 如果没有指定 public 或 private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问

JDK1.2 开始,官方修改了类加载器,禁止加载用户自定义的,以 java 开头的类。

类路径

类路径是所有包含类文件的路径的集合。

类存储在文件系统的子目录中。类的路径必须与包名匹配。

类文件也可以存储在 JAR(Java归档)文件中。在一个 JAR 文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省又可以改善性能。

当程序需要使用第三方的库文件的时候,通过会给出一个或多个 jar 文件使用。JAR 文件使用 ZIP 格式组织文件和子目录。

类路径可以连接 Java 运行库和文件系统。它定义编译器和解释器应该在何处查找要加载的 .class 文件。它的基本思想是:文件系统的层次结构反映了 Java 包的层次结构,而类路径则定义了文件系统中的哪个目录可以作为 Java 包层次结构的根。

设置类路径:-classpath
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar HyProg
java -classpath c:\classdir;.;c:\archives\archive.jar MyProg
设置 CLASSPATH 环境变量
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar 
set CLASSPATH=c:\classdir;.;c:\archives\archive.jar

对象的拷贝

Java 对象想要进行拷贝需要此类实现 Cloneable 接口,默认进行浅拷贝,若先要进行深拷贝需要重写 clone 方法。

如果没有实现 Cloneable 接口,那么会抛出异常 CloneNotSupportedException。

深拷贝

从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

浅拷贝

重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

笔记大部分摘录自《Java核心技术卷I》,含有少数本人修改补充痕迹。

相关文章
|
17天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
21天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
42 17
|
20天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
17天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
12 2
|
29天前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
24 6
|
1月前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
20 3
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
1月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第3天】Java零基础教学篇,手把手实践教学!
16 1
|
20天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
18 0
|
1月前
|
存储 前端开发 Java
你还没有对象吗?java带你创建一个吧
你还没有对象吗?java带你创建一个吧
12 0