Error[8]: Undefined offset: 1277, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

学习目标:
一、面向对象程序设计概述

面向对象程序设计(object-oriented programming,OOP)是当今主流的程序设计范型,由于Java是面向对象的,所以必须熟悉OOP才能很好的使用Java。

面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。

传统的结构化程序设计通过设计一系列的过程(算法)来求解问题,而OOP却调换了这个顺序,将数据放在第一位,然后再考虑 *** 作数据的算法。

对于一些规模较小的问题,将其分解为过程的开发方式比较理想。面向对象更加适合解决规模较大的问题。

(一)类

类(class)是构造对象的模板或蓝图,由类构造(construct)对象的过程称为创建类的实例(instance)。

封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。对象中的数据称为实例字段(instance field), *** 作数据的过程称为方法(method)。作为一个类的实例,特定对象都有一组特
定的实例字段值。这些值的集合就是这个对象的当前状态(state)。

实现封装的关键在于,绝不能让类中的方法直接访问其他类的实例字段。程序只能通过对象的方法与对象数据进行交互(类似黑盒)。

在Java中,所有的类都源自一个“超类”,它就是Object,所有的类都扩展自这个类。

通过扩展一个类来建立另一个类的过程称为继承(inheritance),有关信息会在后续文章中介绍。

(二)对象

我们对象的三个主要特性:

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

每个对象都保存着描述当前状态的信息(状态)。对象状态的改变必须通过调用方法来实现(封装性)。

对象的状态并不能完全描述一个对象。每个对象都有一个唯一的标识(identity)。

对象的这些关键特性会彼此相互影响。

(三)识别类

识别类的一个简单经验是在分析问题的过程中寻找名词,而方法对应着动词。

(四)类之间的关系

在类中,最常见的关系有:

依赖是一种最明显的、最常见的关系。如果一个类的方法使用或 *** 纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能地将相互依赖地类减至最少。

聚合即包含关系。

继承表示一个更特殊的类与一个更一般的类之间的关系。

二、使用预定义类 (一)对象与对象变量

在Java中,要使用构造器(constructor,或称构造函数)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。

构造器的名字应该与类名相同。要想构造一个类对象,需要在构造器前面加上new *** 作符:

new Date();

如果需要的话,也可以将这个对象传递给一个方法:

System.out.println(new Date());

或者也可以对刚刚创建的对象应用一个方法:

String s = new Date();

要注意对象与对象变量之间的区分,对象变量不是一个对象,而它实际上也没有引用任何对象,必须对其进行初始化。有两个选择,一是初始化这个变量,让它引用一个新构造的对象;二是设置这个变量,让它引用一个已有的对象:

Date deadline = new Date();
deadline = birthday;
(二)更改器方法与访问器方法

访问对象且修改对象的方法称为更改器方法(mutator method);访问对象但不修改对象的方法称为访问器方法(accessor method)。

三、用户自定义类

要想构建一个完整的程序,会结合使用多个类,其中只有一个类有main方法。

(一)定义一个类

在Java中,最简单的类定义形式为:

class ClassName
{
	field1;
	field2;
	...
	constructor1;
	constructor2;
	...
	method1;
	method2;
	...
}

下面看一个非常简单的Employee类。在编写工资管理系统时可能会用到:

class Employee
{
	// instance fields
	private String name;
	private double salary;
	private localDate hireDay;
	
	// constructor
	public Employee(String n, double s, int year, int month, int day)
	{
		name = n;
		salary = s;
		hireDay = localDate.of(year, month, day);
	}

	// a method
	public String getName()
	{
		return name;
	}

	// more method
	...
}

在一个源文件中,只能有一个公共类,但可以有任意数目的非公共类。
下面给出程序源代码:

import java.time.*;

/**
 * This program tests the Employee class.
 * @version 1.13 2018-04-10
 * @author Cay Horstmann
 */
public class EmployeeTest
{
   public static void main(String[] args)
   {
      // fill the staff array with three Employee objects
      Employee[] staff = new Employee[3];

      staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

      // raise everyone's salary by 5%
      for (Employee e : staff)
         e.raiseSalary(5);

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay=" 
            + e.getHireDay());
   }
}

class Employee
{
   private String name;
   private double salary;
   private LocalDate hireDay;

   public Employee(String n, double s, int year, int month, int day)
   {
      name = n;
      salary = s;
      hireDay = LocalDate.of(year, month, day);
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public LocalDate getHireDay()
   {
      return hireDay;
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
}
(二)多个源文件的使用

一个源文件包含多个类,部分程序员喜欢将每一个类存在一个单独的源文件中,如果喜欢这样组织文件,有两种编译源程序的方法。
一是使用通配符调用java编译器:

javac Employee*.java

或者键入以下命令:

javac EmployeeTest.java
(三)剖析Employee类

Employee类中包含一个构造器和4个方法:
public Employee(String n, double s, int year, int month, int day)
public String getName()
public double getSalary()
public LocalDate getHireDay()
public void raiseSalary(double byPercent)
所有方法都被标记为public。关键字public意味着任何类的方法都可以调用这些方法。

同时设置了3个实例字段用来存放要 *** 作的数据:
private String name;
private double salary;
private localDate hireDay;
关键字private确保只有Employee类自身才能访问这些字段,而其他类的方法不能读写这些字段。

类包含的实例字段通常属于某个类类型。

(四)构造器

Employee类的构造器如下:

public Employee(String n, double s, int year, int month, int day)
{
   name = n;
   salary = s;
   hireDay = LocalDate.of(year, month, day);
 }

构造器与类同名,在构造Employee类的对象时,构造器会运行,从而将实例字段初始化为所希望的初始状态。

构造器总是结合new运算符来调用。

以下是构造器的几个特性:

注:不要在构造器中定义与实例字段同名的局部变量。
同时可以使用var声明局部变量:

var harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);

注意var关键字只能用于方法中的局部变量,参数和字段的类型必须声明。

(五)使用null引用

一个对象变量包含一个对象的引用,或者包含一个特殊值null,后者表示没有引用任何对象。使用null时,必须明确哪些字段可能为null,不然会出错:

localDate birthday = null;
String s = birthday.toString();	// NullPointerException

对此有两种解决方法:

”宽容型“方法是把null参数转换成为一个适当的非null值:

if (n == null) name = "unknown";
else name = n;

在Java9中,Objects类对此提供了一个便利方法:

public Employee(String n, double s, int year, int month, int day)
{
	name = Objects.requireNonNullElse(n, "unknown");
	...
}

”严格型“方法则是干脆拒绝null参数:

public Employee(String n, double s, int year, int month, int day)
{
	Objects requireNonNull(n, "The name can't be null");
	name = n;
	...
(六)隐式参数与显式参数

方法用于 *** 作对象以及存取它们的实例字段。如:

public void raiseSalary(double byPercent)
{
	double raise = salary * byPercent / 100;
	salary += raise;
}

调用这个方法的对象的salary实例字段设置为一个新值。考虑以下调用:

number07.raiseSalary(5);

调用过程如下:

double raise = number07.salary * 5 / 100;
number07.salary += raise;

raiseSalary方法中有两个参数。第一个参数称为隐式(implicit)参数,是出现在方法明前的Employee类型的对象。第二个参数是位于方法名后括号中的数值,称为显式(explicit)参数。

在每一个方法中,关键字this指示隐式参数,如:

public void raiseSalary(double byPercent)
{
	double raise = this.salary * byPercent / 100;
	this.salary += raise;
}
(七)封装的优点

最后再看下getName方法:

   public String getName()
   {
      return name;
   }

这是典型的访问器方法。只返回实例字段值,因此又称为字段访问器。这样可以保护name字段不会受到外界的破坏。

有时,可能想要获得或设置实例字段的值,就需要提供三项内容:

四、静态字段与静态方法 (一)静态字段

如果将一个字段定义为static,每个类只有一个这样的字段。而对于非静态的实例字段,每个对象都有自己的一个副本。如:

class Employee
{
	private static int nextId = 1;
	private int id;
}

每个Employee对象都有一个自己的id字段,但这个类的所有实例共享一个nextId字段。即使没有Employee对象,静态字段nextId也存在。它属于类,而不属于单个的对象。

(二)静态常量

静态变量用的较少,但静态常量比较常用。如Math类中定义一个静态常量:

public class Math
{
	...
	public static final double PI = 3.14159265358979323846;
	...
}

在程序中,可以使用Math.PI来访问这个常量。

若省略关键字static,PI就变成Math类的一个实例字段。

每个类对象都可以修改公共字段,所以,最好不要有公共字段。公共常量(即final字段)却没问题。

(三)静态方法

静态方法是不在对象上执行的方法。如Math类的pow方法:

Math.pow(x,a)

会计算幂 x 2 x^2 x2。在完成运算时,它并不使用任何Math对象,即没有隐式参数。
以下两种情况可以使用静态方法:

(四)工厂方法

静态方法还有另一种常见的用途,类似LocalDate和NumberFormat的类使用静态工厂方法(factory method)来构造对象。NumberFormat类如下生成不同风格的格式化对象:

NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.3;
System.out.println(currencyFormatter.format(x));	// prints System.10
..outprintln(.percentFormatterformat()x);// prints 10%	
  • 无法命名构造器。构造器的名字必须与类名相同。
  • 并不使用构造器主要是由于:

    (五)main方法

    可以调用静态方法而不需要对象。main方法也是一个静态方法,该方法不对任何对象进行 *** 作。实际上,在启动程序时还没有任何对象。静态的main方法将执行并构造程序所需要的对象。

    五、方法参数

    按值调用(call by value)表示方法接收的是调用者提供的值。而按引用调用(call by reference)表示方法接收的是调用者提供的地址。

    Java程序设计语言总是按值调用。一个方法不可能修改基本数据类型的参数,但可以修改对象引用作为的参数。如:

    static void tripleSalary (Employee) x// works	.
    {
    	xraiseSalary(200);}
    =
    
    harry new Employee (...);tripleSalary
    ()harry;
  • 方法不能修改基本数据类型的参数(即数值型或布尔型)
  • 实际上,Java对对象采用的也不是引用调用。实际上,对象引用是值传递的。

    下面总结下Java中对方法参数能做什么而不能做什么:

    可以尝试运行下列程序进行测试:

    public
    class ParamTest public
    {
       static void main (String[]) args/*
           * Test 1: Methods can't modify numeric parameters
           */
       {
          System
          ..outprintln("Testing tripleValue:");double
          = percent 10 ;System
          ..outprintln("Before: percent="+ ) percent;tripleValue
          ()percent;System
          ..outprintln("After: percent="+ ) percent;/*
           * Test 2: Methods can change the state of object parameters
           */
    
          System
          ..outprintln("\nTesting tripleSalary:");var
          = harry new Employee ("Harry",50000 );System
          ..outprintln("Before: salary="+ . harrygetSalary());tripleSalary
          ()harry;System
          ..outprintln("After: salary="+ . harrygetSalary());/*
           * Test 3: Methods can't attach new objects to object parameters
           */
    
          System
          ..outprintln("\nTesting swap:");var
          = a new Employee ("Alice",70000 );var
          = b new Employee ("Bob",60000 );System
          ..outprintln("Before: a="+ . agetName());System
          ..outprintln("Before: b="+ . bgetName());swap
          (,a) b;System
          ..outprintln("After: a="+ . agetName());System
          ..outprintln("After: b="+ . bgetName());}
       public
    
       static void tripleValue (double) x// doesn't work =
       {
          x 3 * ; xSystem
          ..outprintln("End of method: x="+ ) x;}
       public
    
       static void tripleSalary (Employee) x// works .
       {
          xraiseSalary(200);System
          ..outprintln("End of method: salary="+ . xgetSalary());}
       public
    
       static void swap (Employee, xEmployee ) yEmployee
       {
          = temp ; x=
          x ; y=
          y ; tempSystem
          ..outprintln("End of method: x="+ . xgetName());System
          ..outprintln("End of method: y="+ . ygetName());}
       }
    class
    
    Employee // simplified Employee class private
    {
       String ; nameprivate
       double ; salarypublic
    
       Employee (String, ndouble ) s=
       {
          name ; n=
          salary ; s}
       public
    
       String getName ()return
       {
          ; name}
       public
    
       double getSalary ()return
       {
          ; salary}
       public
    
       void raiseSalary (double) byPercentdouble
       {
          = raise * salary / byPercent 100 ;+=
          salary ; raise}
       }
    var
    
    六、对象构造 (一)重载

    有些类有多个构造器。如可以如下构造一个空的StringBuilder对象:

    = message new StringBuilder ()var
    

    或者指定一个初始字符串:

    = todoList new StringBuilder ("To do:\n");class
    

    这个功能叫做重载(overloading)。如果多个方法有相同的名字、不同的参数,便出现重载。查找匹配的过程称为重载解析(overloading resolution)。

    Java允许重载任何方法,而不只是构造器方法。因此,要完整地描述一个方法,需要指定方法名以及参数类型,这叫做方法的签名(signature)。

    (二)默认字段初始化

    如果构造器中没有显式地为字段设置处值,就会被自动地赋为默认值:数值为0,布尔值为false,对象引用为null。

    (三)无参构造器

    如果一个类没有编写构造器,就会为你提供一个无参构造器。这个构造器将有所得实例字段设为默认值。

    如果类中至少提供了一个构造器,但没有提供无参数的构造器,那么构造对象时如果不提供参数就是不合法的。

    (四)显式字段初始化

    通过重载类的构造器方法,可以采用多种形式设置类的实例字段的初始状态。

    初始值不一定是常量值。如:

    Employee private
    {
    	static int ; nextIdprivate
    	int = id assignId ();.
    	..private
    	static int assignId ()int
    	{
    		= r ; nextId++
    		nextId;return
    		; r}
    	.
    	..}
    public
    
    (五)参数名

    我们通常喜欢用单个字母作为参数名,但这样做只有阅读代码时才能了解参数的含义,有些程序员在每个参数前加上前缀“a”作为参数名。

    还有一种常用技巧:参数变量会遮蔽同名的实例字段。如:

    Employee (String, namedouble ) salarythis
    {
    	.=name ; namethis
    	.=salary ; salary}
    public
    
    (六)调用另一个构造器

    关键字this指示一个方法的隐式参数。不过这个关键字还有另一层含义:
    如果构造器的第一个语句形如 this(…) ,这个构造器将调用同一个类中的另一个构造器,如:

    Employee (double) s// calls Employee(String, double);
    {
    	this
    	("Employee #"+ , nextId) s;++
    	nextId;}
    class
    

    这样对公共的构造器代码只需要编写一次即可。

    (七)初始化块

    Java还有第三种机制,成为初始化块(initialization block)。在一个类的声明中,可以包含任意多个代码块。只要构造这个类的对象,这些块就会被执行。如:

    Employee private
    {
    	static int ; nextIdprivate
    	int ; idprivate
    	String ; nameprivate
    	double ; salary// object initialization block
    
    	=
    	{
    		id ; nextId++
    		nextId;}
    	public
    	
    	Empolyee (String, ndouble ) s=
    	{
    		name ; n=
    		salary ; s}
    	public
    
    	Empolyee ()=
    	{
    		name "" ;=
    		salary 0 ;}
    	.
    	..}
    .
    

    无论调用哪个构造器对象,id字段都会在对象初始化块中初始化,首先运行初始化块,然后才运行构造器的主体部分。

    这种机制不是必须的,通常会直接将初始化代码放在构造器中。

    若在初始化块前加static,则在类第一次加载时,会进行静态字段的初始化。

    (八)对象析构与finalize方法

    由于Java会完成自动的垃圾回收,因此不支持析构器。

    如果对象使用了内存外的其他资源,则需要进行资源的回收和再利用。

    finalize方法目前已被废弃,不作介绍。

    七、包

    Java允许使用包(package)将类组织在一个集合中。

    (一)包名

    使用包的主要原因是确保类名的唯一性。为了保证包名的绝对唯一性,要用一个因特网名以逆序的形式作为包名,然后对不同的工程使用不同的子包。

    (二)类的导入

    一个类可以使用所属包中的所有类,以及其他包中的公共类(public class)。

    我们可以采用两种方式访问另一个包中的公共类。第一种方式是使用完全限定名(fully qualified name);就是包名后面跟类名,如:

    java.time=localDate today . javat.time.localDatenow()import
    

    更常用的是使用import语句。如:`

    . java.time*import
    

    当两个包含有相同的类名时,可以增加一个特定的import语句来解决问题:

    . java.util*import
    . java.sql*import
    . java.utilDate;var
    
    = deadline new . java.util(Date);var
    = today new . java.sql(Date...);import
    
    (三)静态导入

    有一种import语句允许导入静态方法和静态字段,如:

    static . java.langSystem.;*.
    

    就可以使用System类的静态方法和静态字段,而不必加类名前缀:

    outprintln("Goodbye, World!");// i.e., System.out	exit
    (0);// i.e., System.out;	import
    

    另外可以导入特定的方法或字段:

    static . java.langSystem.;outpackage
    
    (四)在包中增加类

    想要将类放入包中,就必须将包的名字放在源文件的开头:

    . com.horstmann;corejavapublic
    
    class Employee .
    {
    	..}
    -
    
    (五)包访问

    标记为public的部分可以由任意类使用,标记为private的部分只能由定义它们的类使用。

    (六)类路径

    类的路径必须和包名匹配。

    另外,类文件也可以存储在JAR(Java归档)文档中。

    最好使用 -classpath(或 -cp, 或Java9中的 --class-path)选项指定类路径:

    java /classpth /home/user:classdir.://home/user/archives.archiveMyProgjar -
    或
    java :classpath c;\classdir.:c.\archives\archiveMyprogjar .
    
    八、JAR文件

    一个JAR文件可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。此外,JAR文件是压缩的,它使用了我们熟悉的ZIP压缩格式。

    (一)创建JAR文件

    可以使用jar工具制作JAR文件,创建命令如下:

    jar cvf jarFileName file1 file2 ..CalculationClasses
    

    如:

    jar cvf .*jar .class. icon.gif
    

    通常jar命令的格式如下:

    jar options file1 file2 ...
    
    (二)清单文件

    每个JAR文件还包含一个清单文件(manifest),用于描述归档文件的特殊性。

    清单文件被命名为MANIFEST.MF,它位于JAR文件一个特殊的META-INF子目录中。

    要创建一个包含清单文件的JAR文件,应执行:

    jar cfm jarFileName manifestFileName ..MyArchive
    

    如:

    jar cfm ..jar manifest/mf com/mycompany/*.class
    mypkgMyArchive

    要更新已有的JAR文件的清单,则应使用:

    jar ufm .-jar manifest.additionsMyProgrammf
    
    (三)可执行JAR文件

    可以使用jar命令中的e选项指定程序的入口点,即通常需要在调用java程序启动器时指定的类:

    jar cvfe ..jar com.mycompany.mypkgtoMainAppClass files add Main
    

    或者,可以在清单文件中指定程序的主类,包括以下形式的语句:

    -Class:. com.mycompany.mypkg-MainAppClass
    

    无论哪种方法,用户都可以通过以下命令启动程序:

    java MyProgramjar .MyProgramjar
    
    (四)多版本JAR文件

    Java9引入了多版本JAR,可以包含面向不同Java版本的类文件。为保证向后兼容,额外的类文件放在META-INF/versions目录中。

    要增加不同版本的类文件,可以使用 --release 标志:

    jar uf .--jar 9release Application .classMyProgram
    

    要从头构建一个多版本JAR文件,可以使用 -C 选项:

    jar cf .-jar C/ bin8. -- 9release - /c bin9Application .class-
    

    面向不同版本编译时,要使用 --release标志和 -d标志来指定输出目录:

    javac /d bin8-- 8release . ..
  • 模块
  • 九、文档注释

    JDK包含了一个很有用的工具,叫javadoc,可以有源文件生成一个HTML文档。

    javadoc实用工具从下面几项中抽取信息:

    每个/**…*/文档注释包含标记以及之后紧跟着的自由格式文本,如:

    	public
    	class Card .
    	{
    		..}
    	
  • 一定要保证数据私有
  • 注释有类注释、方法注释、字段注释、通用注释、包注释等,这里不一一介绍

    十、类设计技巧

    这里介绍了一些简单的类设计技巧:


    参考资料:

    狂神说Java
    Java核心技术 卷I(第11版)


    上一章:Java从零开始系列01:Java入门
    下一章:Java从零开始系列03:继承

    )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    Java从零开始系列02:对象与类_java_内存溢出

    Java从零开始系列02:对象与类

    Java从零开始系列02:对象与类,第1张

    学习目标:
    • 面向对象程序设计入门
    • 如何创建标准Java类库中的对象
    • 如何编写自己的类

    一、面向对象程序设计概述

    面向对象程序设计(object-oriented programming,OOP)是当今主流的程序设计范型,由于Java是面向对象的,所以必须熟悉OOP才能很好的使用Java。

    面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。

    传统的结构化程序设计通过设计一系列的过程(算法)来求解问题,而OOP却调换了这个顺序,将数据放在第一位,然后再考虑 *** 作数据的算法。

    对于一些规模较小的问题,将其分解为过程的开发方式比较理想。面向对象更加适合解决规模较大的问题。

    (一)类

    类(class)是构造对象的模板或蓝图,由类构造(construct)对象的过程称为创建类的实例(instance)。

    封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。对象中的数据称为实例字段(instance field), *** 作数据的过程称为方法(method)。作为一个类的实例,特定对象都有一组特
    定的实例字段值。这些值的集合就是这个对象的当前状态(state)。

    实现封装的关键在于,绝不能让类中的方法直接访问其他类的实例字段。程序只能通过对象的方法与对象数据进行交互(类似黑盒)。

    在Java中,所有的类都源自一个“超类”,它就是Object,所有的类都扩展自这个类。

    通过扩展一个类来建立另一个类的过程称为继承(inheritance),有关信息会在后续文章中介绍。

    (二)对象

    我们对象的三个主要特性:

    • 对象的行为(behavior)–可以对对象完成哪些 *** 作,或者可以对对象使用哪些方法?
    • 对象的状态(state)–当调用那些方法时,对象会如何响应?
    • 对象的标识(identity)–如何区分具有相同行为与状态的不同对象?

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

    每个对象都保存着描述当前状态的信息(状态)。对象状态的改变必须通过调用方法来实现(封装性)。

    对象的状态并不能完全描述一个对象。每个对象都有一个唯一的标识(identity)。

    对象的这些关键特性会彼此相互影响。

    (三)识别类

    识别类的一个简单经验是在分析问题的过程中寻找名词,而方法对应着动词。

    (四)类之间的关系

    在类中,最常见的关系有:

    • 依赖(“uses-a“)
    • 聚合(”has-a“)
    • 继承(“is-a”)

    依赖是一种最明显的、最常见的关系。如果一个类的方法使用或 *** 纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能地将相互依赖地类减至最少。

    聚合即包含关系。

    继承表示一个更特殊的类与一个更一般的类之间的关系。

    二、使用预定义类 (一)对象与对象变量

    在Java中,要使用构造器(constructor,或称构造函数)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。

    构造器的名字应该与类名相同。要想构造一个类对象,需要在构造器前面加上new *** 作符:

    new Date();
    

    如果需要的话,也可以将这个对象传递给一个方法:

    System.out.println(new Date());
    

    或者也可以对刚刚创建的对象应用一个方法:

    String s = new Date();
    

    要注意对象与对象变量之间的区分,对象变量不是一个对象,而它实际上也没有引用任何对象,必须对其进行初始化。有两个选择,一是初始化这个变量,让它引用一个新构造的对象;二是设置这个变量,让它引用一个已有的对象:

    Date deadline = new Date();
    deadline = birthday;
    
    (二)更改器方法与访问器方法

    访问对象且修改对象的方法称为更改器方法(mutator method);访问对象但不修改对象的方法称为访问器方法(accessor method)。

    三、用户自定义类

    要想构建一个完整的程序,会结合使用多个类,其中只有一个类有main方法。

    (一)定义一个类

    在Java中,最简单的类定义形式为:

    class ClassName
    {
    	field1;
    	field2;
    	...
    	constructor1;
    	constructor2;
    	...
    	method1;
    	method2;
    	...
    }
    

    下面看一个非常简单的Employee类。在编写工资管理系统时可能会用到:

    class Employee
    {
    	// instance fields
    	private String name;
    	private double salary;
    	private localDate hireDay;
    	
    	// constructor
    	public Employee(String n, double s, int year, int month, int day)
    	{
    		name = n;
    		salary = s;
    		hireDay = localDate.of(year, month, day);
    	}
    
    	// a method
    	public String getName()
    	{
    		return name;
    	}
    
    	// more method
    	...
    }
    

    在一个源文件中,只能有一个公共类,但可以有任意数目的非公共类。
    下面给出程序源代码:

    import java.time.*;
    
    /**
     * This program tests the Employee class.
     * @version 1.13 2018-04-10
     * @author Cay Horstmann
     */
    public class EmployeeTest
    {
       public static void main(String[] args)
       {
          // fill the staff array with three Employee objects
          Employee[] staff = new Employee[3];
    
          staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
          staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
          staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
    
          // raise everyone's salary by 5%
          for (Employee e : staff)
             e.raiseSalary(5);
    
          // print out information about all Employee objects
          for (Employee e : staff)
             System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay=" 
                + e.getHireDay());
       }
    }
    
    class Employee
    {
       private String name;
       private double salary;
       private LocalDate hireDay;
    
       public Employee(String n, double s, int year, int month, int day)
       {
          name = n;
          salary = s;
          hireDay = LocalDate.of(year, month, day);
       }
    
       public String getName()
       {
          return name;
       }
    
       public double getSalary()
       {
          return salary;
       }
    
       public LocalDate getHireDay()
       {
          return hireDay;
       }
    
       public void raiseSalary(double byPercent)
       {
          double raise = salary * byPercent / 100;
          salary += raise;
       }
    }
    
    (二)多个源文件的使用

    一个源文件包含多个类,部分程序员喜欢将每一个类存在一个单独的源文件中,如果喜欢这样组织文件,有两种编译源程序的方法。
    一是使用通配符调用java编译器:

    javac Employee*.java
    

    或者键入以下命令:

    javac EmployeeTest.java
    
    (三)剖析Employee类

    Employee类中包含一个构造器和4个方法:
    public Employee(String n, double s, int year, int month, int day)
    public String getName()
    public double getSalary()
    public LocalDate getHireDay()
    public void raiseSalary(double byPercent)
    所有方法都被标记为public。关键字public意味着任何类的方法都可以调用这些方法。

    同时设置了3个实例字段用来存放要 *** 作的数据:
    private String name;
    private double salary;
    private localDate hireDay;
    关键字private确保只有Employee类自身才能访问这些字段,而其他类的方法不能读写这些字段。

    类包含的实例字段通常属于某个类类型。

    (四)构造器

    Employee类的构造器如下:

    public Employee(String n, double s, int year, int month, int day)
    {
       name = n;
       salary = s;
       hireDay = LocalDate.of(year, month, day);
     }
    

    构造器与类同名,在构造Employee类的对象时,构造器会运行,从而将实例字段初始化为所希望的初始状态。

    构造器总是结合new运算符来调用。

    以下是构造器的几个特性:

    • 构造器与类同名
    • 每个类可以有一个以上的构造器
    • 构造器可以有0个、1个或多个参数
    • 构造器没有返回值
    • 构造器总是伴随着new *** 作符一起调用

    注:不要在构造器中定义与实例字段同名的局部变量。
    同时可以使用var声明局部变量:

    var harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
    

    注意var关键字只能用于方法中的局部变量,参数和字段的类型必须声明。

    (五)使用null引用

    一个对象变量包含一个对象的引用,或者包含一个特殊值null,后者表示没有引用任何对象。使用null时,必须明确哪些字段可能为null,不然会出错:

    localDate birthday = null;
    String s = birthday.toString();	// NullPointerException
    

    对此有两种解决方法:

    ”宽容型“方法是把null参数转换成为一个适当的非null值:

    if (n == null) name = "unknown";
    else name = n;
    

    在Java9中,Objects类对此提供了一个便利方法:

    public Employee(String n, double s, int year, int month, int day)
    {
    	name = Objects.requireNonNullElse(n, "unknown");
    	...
    }
    

    ”严格型“方法则是干脆拒绝null参数:

    public Employee(String n, double s, int year, int month, int day)
    {
    	Objects requireNonNull(n, "The name can't be null");
    	name = n;
    	...
    
    (六)隐式参数与显式参数

    方法用于 *** 作对象以及存取它们的实例字段。如:

    public void raiseSalary(double byPercent)
    {
    	double raise = salary * byPercent / 100;
    	salary += raise;
    }
    

    调用这个方法的对象的salary实例字段设置为一个新值。考虑以下调用:

    number07.raiseSalary(5);
    

    调用过程如下:

    double raise = number07.salary * 5 / 100;
    number07.salary += raise;
    

    raiseSalary方法中有两个参数。第一个参数称为隐式(implicit)参数,是出现在方法明前的Employee类型的对象。第二个参数是位于方法名后括号中的数值,称为显式(explicit)参数。

    在每一个方法中,关键字this指示隐式参数,如:

    public void raiseSalary(double byPercent)
    {
    	double raise = this.salary * byPercent / 100;
    	this.salary += raise;
    }
    
    (七)封装的优点

    最后再看下getName方法:

       public String getName()
       {
          return name;
       }
    

    这是典型的访问器方法。只返回实例字段值,因此又称为字段访问器。这样可以保护name字段不会受到外界的破坏。

    有时,可能想要获得或设置实例字段的值,就需要提供三项内容:

    • 一个私有的数据字段
    • 一个公共的字段访问器方法
    • 一个公共的字段更改器方法
    四、静态字段与静态方法 (一)静态字段

    如果将一个字段定义为static,每个类只有一个这样的字段。而对于非静态的实例字段,每个对象都有自己的一个副本。如:

    class Employee
    {
    	private static int nextId = 1;
    	private int id;
    }
    

    每个Employee对象都有一个自己的id字段,但这个类的所有实例共享一个nextId字段。即使没有Employee对象,静态字段nextId也存在。它属于类,而不属于单个的对象。

    (二)静态常量

    静态变量用的较少,但静态常量比较常用。如Math类中定义一个静态常量:

    public class Math
    {
    	...
    	public static final double PI = 3.14159265358979323846;
    	...
    }
    

    在程序中,可以使用Math.PI来访问这个常量。

    若省略关键字static,PI就变成Math类的一个实例字段。

    每个类对象都可以修改公共字段,所以,最好不要有公共字段。公共常量(即final字段)却没问题。

    (三)静态方法

    静态方法是不在对象上执行的方法。如Math类的pow方法:

    Math.pow(x,a)
    

    会计算幂 x 2 x^2 x2。在完成运算时,它并不使用任何Math对象,即没有隐式参数。
    以下两种情况可以使用静态方法:

    • 方法不需要访问对象状态,因此它需要的所有参数都通过显式参数提供
    • 方法只需要访问类的静态字段
    (四)工厂方法

    静态方法还有另一种常见的用途,类似LocalDate和NumberFormat的类使用静态工厂方法(factory method)来构造对象。NumberFormat类如下生成不同风格的格式化对象:

    NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
    NumberFormat percentFormatter = NumberFormat.getPercentInstance();
    double x = 0.3;
    System.out.println(currencyFormatter.format(x));	// prints System.10
    ..outprintln(.percentFormatterformat()x);// prints 10%	
  • 无法命名构造器。构造器的名字必须与类名相同。
  • 并不使用构造器主要是由于:

    • 使用构造器时,无法改变构造对象的类型。而工厂方法实际上返回DecimalFormat类的对象,这是NumberFormat的一个子类。
    • public
    (五)main方法

    可以调用静态方法而不需要对象。main方法也是一个静态方法,该方法不对任何对象进行 *** 作。实际上,在启动程序时还没有任何对象。静态的main方法将执行并构造程序所需要的对象。

    五、方法参数

    按值调用(call by value)表示方法接收的是调用者提供的值。而按引用调用(call by reference)表示方法接收的是调用者提供的地址。

    Java程序设计语言总是按值调用。一个方法不可能修改基本数据类型的参数,但可以修改对象引用作为的参数。如:

    static void tripleSalary (Employee) x// works	.
    {
    	xraiseSalary(200);}
    =
    
    harry new Employee (...);tripleSalary
    ()harry;
  • 方法不能修改基本数据类型的参数(即数值型或布尔型)
  • 实际上,Java对对象采用的也不是引用调用。实际上,对象引用是值传递的。

    下面总结下Java中对方法参数能做什么而不能做什么:

    • 方法可以改变对象参数的状态
    • 方法不能让一个对象参数引用一个新的对象
    • /** * This program demonstrates parameter passing in Java. * @version 1.01 2018-04-10 * @author Cay Horstmann */

    可以尝试运行下列程序进行测试:

    public
    class ParamTest public
    {
       static void main (String[]) args/*
           * Test 1: Methods can't modify numeric parameters
           */
       {
          System
          ..outprintln("Testing tripleValue:");double
          = percent 10 ;System
          ..outprintln("Before: percent="+ ) percent;tripleValue
          ()percent;System
          ..outprintln("After: percent="+ ) percent;/*
           * Test 2: Methods can change the state of object parameters
           */
    
          System
          ..outprintln("\nTesting tripleSalary:");var
          = harry new Employee ("Harry",50000 );System
          ..outprintln("Before: salary="+ . harrygetSalary());tripleSalary
          ()harry;System
          ..outprintln("After: salary="+ . harrygetSalary());/*
           * Test 3: Methods can't attach new objects to object parameters
           */
    
          System
          ..outprintln("\nTesting swap:");var
          = a new Employee ("Alice",70000 );var
          = b new Employee ("Bob",60000 );System
          ..outprintln("Before: a="+ . agetName());System
          ..outprintln("Before: b="+ . bgetName());swap
          (,a) b;System
          ..outprintln("After: a="+ . agetName());System
          ..outprintln("After: b="+ . bgetName());}
       public
    
       static void tripleValue (double) x// doesn't work =
       {
          x 3 * ; xSystem
          ..outprintln("End of method: x="+ ) x;}
       public
    
       static void tripleSalary (Employee) x// works .
       {
          xraiseSalary(200);System
          ..outprintln("End of method: salary="+ . xgetSalary());}
       public
    
       static void swap (Employee, xEmployee ) yEmployee
       {
          = temp ; x=
          x ; y=
          y ; tempSystem
          ..outprintln("End of method: x="+ . xgetName());System
          ..outprintln("End of method: y="+ . ygetName());}
       }
    class
    
    Employee // simplified Employee class private
    {
       String ; nameprivate
       double ; salarypublic
    
       Employee (String, ndouble ) s=
       {
          name ; n=
          salary ; s}
       public
    
       String getName ()return
       {
          ; name}
       public
    
       double getSalary ()return
       {
          ; salary}
       public
    
       void raiseSalary (double) byPercentdouble
       {
          = raise * salary / byPercent 100 ;+=
          salary ; raise}
       }
    var
    
    六、对象构造 (一)重载

    有些类有多个构造器。如可以如下构造一个空的StringBuilder对象:

    = message new StringBuilder ()var
    

    或者指定一个初始字符串:

    = todoList new StringBuilder ("To do:\n");class
    

    这个功能叫做重载(overloading)。如果多个方法有相同的名字、不同的参数,便出现重载。查找匹配的过程称为重载解析(overloading resolution)。

    Java允许重载任何方法,而不只是构造器方法。因此,要完整地描述一个方法,需要指定方法名以及参数类型,这叫做方法的签名(signature)。

    (二)默认字段初始化

    如果构造器中没有显式地为字段设置处值,就会被自动地赋为默认值:数值为0,布尔值为false,对象引用为null。

    (三)无参构造器

    如果一个类没有编写构造器,就会为你提供一个无参构造器。这个构造器将有所得实例字段设为默认值。

    如果类中至少提供了一个构造器,但没有提供无参数的构造器,那么构造对象时如果不提供参数就是不合法的。

    (四)显式字段初始化

    通过重载类的构造器方法,可以采用多种形式设置类的实例字段的初始状态。

    初始值不一定是常量值。如:

    Employee private
    {
    	static int ; nextIdprivate
    	int = id assignId ();.
    	..private
    	static int assignId ()int
    	{
    		= r ; nextId++
    		nextId;return
    		; r}
    	.
    	..}
    public
    
    (五)参数名

    我们通常喜欢用单个字母作为参数名,但这样做只有阅读代码时才能了解参数的含义,有些程序员在每个参数前加上前缀“a”作为参数名。

    还有一种常用技巧:参数变量会遮蔽同名的实例字段。如:

    Employee (String, namedouble ) salarythis
    {
    	.=name ; namethis
    	.=salary ; salary}
    public
    
    (六)调用另一个构造器

    关键字this指示一个方法的隐式参数。不过这个关键字还有另一层含义:
    如果构造器的第一个语句形如 this(…) ,这个构造器将调用同一个类中的另一个构造器,如:

    Employee (double) s// calls Employee(String, double);
    {
    	this
    	("Employee #"+ , nextId) s;++
    	nextId;}
    class
    

    这样对公共的构造器代码只需要编写一次即可。

    (七)初始化块

    Java还有第三种机制,成为初始化块(initialization block)。在一个类的声明中,可以包含任意多个代码块。只要构造这个类的对象,这些块就会被执行。如:

    Employee private
    {
    	static int ; nextIdprivate
    	int ; idprivate
    	String ; nameprivate
    	double ; salary// object initialization block
    
    	=
    	{
    		id ; nextId++
    		nextId;}
    	public
    	
    	Empolyee (String, ndouble ) s=
    	{
    		name ; n=
    		salary ; s}
    	public
    
    	Empolyee ()=
    	{
    		name "" ;=
    		salary 0 ;}
    	.
    	..}
    .
    

    无论调用哪个构造器对象,id字段都会在对象初始化块中初始化,首先运行初始化块,然后才运行构造器的主体部分。

    这种机制不是必须的,通常会直接将初始化代码放在构造器中。

    若在初始化块前加static,则在类第一次加载时,会进行静态字段的初始化。

    (八)对象析构与finalize方法

    由于Java会完成自动的垃圾回收,因此不支持析构器。

    如果对象使用了内存外的其他资源,则需要进行资源的回收和再利用。

    finalize方法目前已被废弃,不作介绍。

    七、包

    Java允许使用包(package)将类组织在一个集合中。

    (一)包名

    使用包的主要原因是确保类名的唯一性。为了保证包名的绝对唯一性,要用一个因特网名以逆序的形式作为包名,然后对不同的工程使用不同的子包。

    (二)类的导入

    一个类可以使用所属包中的所有类,以及其他包中的公共类(public class)。

    我们可以采用两种方式访问另一个包中的公共类。第一种方式是使用完全限定名(fully qualified name);就是包名后面跟类名,如:

    java.time=localDate today . javat.time.localDatenow()import
    

    更常用的是使用import语句。如:`

    . java.time*import
    

    当两个包含有相同的类名时,可以增加一个特定的import语句来解决问题:

    . java.util*import
    . java.sql*import
    . java.utilDate;var
    
    = deadline new . java.util(Date);var
    = today new . java.sql(Date...);import
    
    (三)静态导入

    有一种import语句允许导入静态方法和静态字段,如:

    static . java.langSystem.;*.
    

    就可以使用System类的静态方法和静态字段,而不必加类名前缀:

    outprintln("Goodbye, World!");// i.e., System.out	exit
    (0);// i.e., System.out;	import
    

    另外可以导入特定的方法或字段:

    static . java.langSystem.;outpackage
    
    (四)在包中增加类

    想要将类放入包中,就必须将包的名字放在源文件的开头:

    . com.horstmann;corejavapublic
    
    class Employee .
    {
    	..}
    -
    
    (五)包访问

    标记为public的部分可以由任意类使用,标记为private的部分只能由定义它们的类使用。

    (六)类路径

    类的路径必须和包名匹配。

    另外,类文件也可以存储在JAR(Java归档)文档中。

    最好使用 -classpath(或 -cp, 或Java9中的 --class-path)选项指定类路径:

    java /classpth /home/user:classdir.://home/user/archives.archiveMyProgjar -
    或
    java :classpath c;\classdir.:c.\archives\archiveMyprogjar .
    
    八、JAR文件

    一个JAR文件可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。此外,JAR文件是压缩的,它使用了我们熟悉的ZIP压缩格式。

    (一)创建JAR文件

    可以使用jar工具制作JAR文件,创建命令如下:

    jar cvf jarFileName file1 file2 ..CalculationClasses
    

    如:

    jar cvf .*jar .class. icon.gif
    

    通常jar命令的格式如下:

    jar options file1 file2 ...
    
    (二)清单文件

    每个JAR文件还包含一个清单文件(manifest),用于描述归档文件的特殊性。

    清单文件被命名为MANIFEST.MF,它位于JAR文件一个特殊的META-INF子目录中。

    要创建一个包含清单文件的JAR文件,应执行:

    jar cfm jarFileName manifestFileName ..MyArchive
    

    如:

    jar cfm ..jar manifest/mf com/mycompany/*.class
    mypkgMyArchive

    要更新已有的JAR文件的清单,则应使用:

    jar ufm .-jar manifest.additionsMyProgrammf
    
    (三)可执行JAR文件

    可以使用jar命令中的e选项指定程序的入口点,即通常需要在调用java程序启动器时指定的类:

    jar cvfe ..jar com.mycompany.mypkgtoMainAppClass files add Main
    

    或者,可以在清单文件中指定程序的主类,包括以下形式的语句:

    -Class:. com.mycompany.mypkg-MainAppClass
    

    无论哪种方法,用户都可以通过以下命令启动程序:

    java MyProgramjar .MyProgramjar
    
    (四)多版本JAR文件

    Java9引入了多版本JAR,可以包含面向不同Java版本的类文件。为保证向后兼容,额外的类文件放在META-INF/versions目录中。

    要增加不同版本的类文件,可以使用 --release 标志:

    jar uf .--jar 9release Application .classMyProgram
    

    要从头构建一个多版本JAR文件,可以使用 -C 选项:

    jar cf .-jar C/ bin8. -- 9release - /c bin9Application .class-
    

    面向不同版本编译时,要使用 --release标志和 -d标志来指定输出目录:

    javac /d bin8-- 8release . ..
  • 模块
  • 九、文档注释

    JDK包含了一个很有用的工具,叫javadoc,可以有源文件生成一个HTML文档。

    javadoc实用工具从下面几项中抽取信息:

    • 公共类与接口
    • 公共的和受保护的字段
    • 公共的和受保护的构造器及方法
    • /** * A..... * ... * ... */

    每个/**…*/文档注释包含标记以及之后紧跟着的自由格式文本,如:

    	public
    	class Card .
    	{
    		..}
    	
  • 一定要保证数据私有
  • 注释有类注释、方法注释、字段注释、通用注释、包注释等,这里不一一介绍

    十、类设计技巧

    这里介绍了一些简单的类设计技巧:

    • 一定要对数据进行初始化
    • 不要在类中使用过多的基本类型
    • 不是所有的字段都需要单独的字段访问器和字段更改器
    • 分解有过多职责的类
    • 类名和方法名要能够体现它们的职责
    • 优先使用不可变的类

    参考资料:

    狂神说Java
    Java核心技术 卷I(第11版)


    上一章:Java从零开始系列01:Java入门
    下一章:Java从零开始系列03:继承

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/langs/794613.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2022-05-06
    下一篇 2022-05-06

    发表评论

    登录后才能评论

    评论列表(0条)

    保存