码出高效:Java开发手册-第2章(6)

简介: 本章开始讲解面向对象思想,并以Java 为载体讲述面向对象思想在具体编程语言中的运用与实践。当前主流的编程语言有50 种左右,主要分为两大阵营:面向对象编程与面向过程编程。面向对象编程(Object-Oriented Programming,OOP)是划时代的编程思想变革,推动了高级语言的快速发展和工业化进程。OOP 的抽象、封装、继承、多态的理念使软件大规模化成为可能,有效地降低了软件开发成本、维护成本和复用成本。面向对象编程思想完全不同于传统的面向过程编程思想,使大型软件的开发就像搭积木一样隔离可控、高效简单,是当今编程领域的一股势不可......

2.4 方法

2.4.1 方法签名

      方法签名包括方法名称和参数列表,是JVM标识方法的唯一索引,不包括返回值,更加不包括访问权限控制符、异常类型等。假如返回值可以是方法签名的一部分,仅从代码可读性角度来考虑,如下示例:

long f() {

      return 1L;

}

double f() {

      return 1.0d;

}

var a = f();

      那么类型推断的var 到底是接收1.0d 还是1L ?从静态阅读的角度,根本无从知道它调用的是哪个方法。

2.4.2 参数

      在高中数学中计算函数f (x ,y )=x 2+2y - 3,将x =3,y =7 代入公式得到 32+2×7 -3=20,这里f (x ,y ) 的x 与y 就是形式参数,简称形参;而3 与7 是实际参数,简称实参。参数是自变量,而f (x ,y ) 函数,即代码中的方法是因变量,是一个逻辑执行的果。参数又叫parameter,在代码注释中用@param 表示参数类型。参数在方法中,属于方法签名的一部分,包括参数类型和参数个数,多个参数用逗号相隔,在代码风格中,约定每个逗号后必须要有一个空格,不管是形参,还是实参。形参是在方法定义阶段,而实参是在方法调用阶段,先来看看实参传递给形参的过程:

public class ParamPassing {

      private static int intStatic = 222;

      private static String stringStatic = "old string";

      private static StringBuilder stringBuilderStatic

             = new StringBuilder("old stringBuilder");

      public static void main(String[] args) {

             // 实参调用

             method(intStatic);

             method(stringStatic);

             method(stringBuilderStatic, stringBuilderStatic);

             // 输出依然是222 ( 第1处)

             System.out.println(intStatic);

             method();

             // 无参方法调用之后,反而修改为888 ( 第2处)

             System.out.println(intStatic);

             // 输出依然是:old string

             System.out.println(stringStatic);

             // 输出结果参考下方分析

             System.out.println(stringBuilderStatic);

      }

      // A 方法

      public static void method(int intStatic) {

             intStatic = 777;

      }

      // B 方法

      public static void method() {

             intStatic = 888;

      }

      // C 方法

      public static void method(String stringStatic) {

             // String 是immutable 对象,String 没有提供任何方法用于修改对象

             stringStatic = "new string";

      }

      // D 方法

      public static void method(StringBuilder stringBuilderStatic1,

             StringBuilder stringBuilderStatic2) {

             // 直接使用参数引用修改对象 ( 第3处)

             stringBuilderStatic1.append(".method.first-");

             stringBuilderStatic2.append("method.second-");

             // 引用重新赋值

             stringBuilderStatic1 = new StringBuilder("new stringBuilder");

             stringBuilderStatic1.append("new method's append");

      }

}

      如果不了解形参与实参的传递方式,对于第1 处和第2 处是存在疑问的。第1处,通过有参方法执行intStatic=777,居然没有修改成功,而使用无参的method 方法却成功地把静态变量intStatic 的值修改为888。字节码实现如图2-6 所示。

11.jpg

图2-6 字节码示意图


      有参的A 方法字节码如图2-6(a)所示,参数是局部变量,拷贝静态变量的777,并存入虚拟机栈中的局部变量表的第一个小格子内。虽然在方法内部的intStatic与静态变量同名,但是因为作用域就近原则,它是局部变量的参数,所有的操作与静态变量是无关的。而无参的B 方法字节码如图2-6(b)所示,先把本地赋值的888压入虚拟机栈中的操作栈,然后给静态变量intStatic 赋值。有两个参数的D 方法中,我们再分析第3 处StringBuilder 的疑问:

public static method(Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;)V

   L0

   ALOAD 0

   LDC ".method.first"

   INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)

   Ljava/lang/StringBuilder;

   POP

L1

   ALOAD 1

   LDC "method.second"

   INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)

   Ljava/lang/StringBuilder;

   POP

L2

   NEW java/lang/StringBuilder

   DUP

   LDC "new stringBuilder"

   INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V

   ASTORE 0

L3

   ALOAD 0

   LDC "new method's append"

   INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)

   Ljava/lang/StringBuilder;

   POP

   RETURN

      注意上述字节码中的两个ALOAD 0,是把静态变量的引用赋值给虚拟机栈的栈帧中的局部变量表,然后ALOAD 操作是把两个对象引用变量压入操作栈的栈顶。注意,这两个引用都指向了静态引用变量指向的new StringBuilder("old stringBuilder") 对象在method(stringBuilderStatic, stringBuilderStatic) 的执行结果后的值,其中红绿字符串分别是两次append 的结果:

      old stringBuilder.method.first-method.second-

      在D 方法中,new 出来一个新的StringBuilder 对象,赋值给stringBuilderStatic1。注意,这是一个新的局部引用变量,使用ASTORE 命令对局部变量表的第一个位置的引用变量值进行了覆盖,然后再重新进行ALOAD 到操作栈顶,所以后续对于stringBuilderStatic1 的append 操作,与类的静态引用变量stringBuilderStatic 没有何关系。

      综上所述,无论是对于基本数据类型,还是引用变量,Java 中的参数传递都是值复制的传递过程。对于引用变量,复制指向对象的首地址,双方都可以通过自己的引用变量修改指向对象的相关属性。

      再来介绍一种特殊的参数——可变参数。它是在JDK5 版本中引入的,主要为了解决当时的反射机制和printf 方法问题,适用于不确定参数个数的场景。可变参数通过“参数类型...”的方式定义,如PrintStream 类中printf 方法使用了可变参数:

public PrintStream printf(String format, Object... args) {

      return format(format, args);

}

// 调用printf 方法示例

System.out.printf("%d", n); (第1处)

System.out.printf("%d %s", n, "something"); (第2处)

      如上示例代码,虽然第1 处调用传入了两个参数,第2处调用传入了三个参数,但它们调用的都是printf(String format, Object ... args) 方法。看上去可变参数使方法调用更简单,省去了手工创建数组的麻烦。有人说可变参数是语法糖,个人觉得是恶魔果实。如果在实际开发过程中使用不当,会严重影响代码的可读性和可维护性。因此,使用时要谨慎小心,尽量不要使用可变参数编程。如果一定要使用,则只有相同参数类型,相同业务含义的参数才可以,并且一个方法中只能有一个可变参数,且这个可变参数必须是该方法的最后一个参数。此外,建议不要使用Object 作为可变参数,如下警示代码:

public static void listUsers(Object... args) {

      System.out.println(args.length);

}

public static void main(String[] args) {

      // 以下代码输出结果为:3

      listUsers(1, 2, 3);

      // 以下代码输出结果为:1

      listUsers(new int[] {1, 2, 3});

      // 以下代码输出结果为:2 ( 第1处)

      listUsers(3, new String[] {"1", "2"});

      // 以下代码输出结果为:3 ( 第2处)

      listUsers(new Integer[] {1, 2, 3});

      // 以下代码输出结果为:2 ( 第3处)

      listUsers(3, new Integer[] {1, 2, 3});

}

      通过上面的例子可以看到,使用Object 作为可变参数时过于灵活,类型转换场景不好预判,比如第2 处和第3 处中Integer[] 可以转型为Object[],也可以作为一个Object 对象,所以导致第2 处输出结果为3,第3 处输出结果为2。而int[] 只能被当作一个单纯的Object 对象。同时Object 又很容易破坏“可变参数具备相同类型,相同业务含义”这个大前提,如上例中第1 处的整型和字符串数组类型混用,因此要避免使用Object 作为可变参数。

      以上是参数定义的相关内容,那么如何正确地使用参数呢?方法定义方并不能保证调用方会按照预期传入参数,因此在方法体中应该对传入的参数保持理性的不信任。方法的第一步骤并不是功能实现,而应该是参数预处理。参数预处理包括两种:

(1)入参保护。虽然“入参保护”被提及的频率和认知度远低于参数校验,但是其重要性却不能被忽略。入参保护实质上是对服务提供方的保护,常见于批量接口。批量接口是指能同时处理一批数据,但其处理能力并不是无限的,因此需要对入参的数据量进行判断和控制,如果超出处理能力,可以直接返回错误给客户端。某业务曾发生过一个严重故障,就是由一个用户批量查询的接口导致的。虽然在API 文档中约定了每次最多支持查询的用户ID 个数,但在接口实现中没有做任何入参保护,导致当调用方传入万级的用户ID 集合查询信息时,服务器内存被塞满,进程假死,再无任何处理能力。

(2)参数校验。参数作为方法间交互和传递信息的媒介,其重要性不言而喻。基于防御式编程理念,在方法内,对方法调用方传入的参数理性上保持不信任,所以对参数有效值的检测都是非常有必要的。由于方法间交互是非常频繁的,如果所有方法都进行参数校验,就会导致重复代码及不必要的检查影响代码性能。综合两个方面考虑,汇总需要进行参数校验和无须校验的场景。

      需要进行参数校验的场景:

  • 调用频度低的方法。
  • 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退或者错误,则得不偿失。
  • 需要极高稳定性和可用性的方法。
  • 对外提供的开放接口。
  • 敏感权限入口。

      不需要进行参数校验的场景:

极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查。

  • 底层调用频度较高的方法。参数错误不太可能到底层才会暴露问题。一般DAO 层与Service 层都在同一个应用中,部署在同一台服务器中,所以可以省略DAO 的参数校验。
  • 声明成 private 只会被自己代码调用的方法。如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
相关文章
|
11天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的服装商城管理系统
基于Java+Springboot+Vue开发的服装商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的服装商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
33 2
基于Java+Springboot+Vue开发的服装商城管理系统
|
9天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
基于Java+Springboot+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的大学竞赛报名管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
23 3
基于Java+Springboot+Vue开发的大学竞赛报名管理系统
|
10天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的蛋糕商城管理系统
基于Java+Springboot+Vue开发的蛋糕商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的蛋糕商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
21 3
基于Java+Springboot+Vue开发的蛋糕商城管理系统
|
10天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的美容预约管理系统
基于Java+Springboot+Vue开发的美容预约管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的美容预约管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
21 3
基于Java+Springboot+Vue开发的美容预约管理系统
|
11天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的房产销售管理系统
基于Java+Springboot+Vue开发的房产销售管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的房产销售管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
25 3
基于Java+Springboot+Vue开发的房产销售管理系统
|
12天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的反诈视频宣传系统
基于Java+Springboot+Vue开发的反诈视频宣传系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的反诈视频宣传管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
41 4
基于Java+Springboot+Vue开发的反诈视频宣传系统
|
13天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的健身房管理系统
基于Java+Springboot+Vue开发的健身房管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的健身房管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
42 5
基于Java+Springboot+Vue开发的健身房管理系统
|
10天前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
12天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的医院门诊预约挂号系统
基于Java+Springboot+Vue开发的医院门诊预约挂号系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的门诊预约挂号管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
33 2
基于Java+Springboot+Vue开发的医院门诊预约挂号系统
|
13天前
|
前端开发 JavaScript Java
基于Java+Springboot+Vue开发的家具管理系统
基于Java+Springboot+Vue开发的家具管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的家具管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
32 2
基于Java+Springboot+Vue开发的家具管理系统
下一篇
无影云桌面