拾——重复调用的代码块

在本质上,一个类描述了两件事情。

1.一个对象知道什么( what's an object knows )?

2.一个对象能做什么( what's an object does )?

第一件事情对应于对象的属性( 或状态 ),第二件事情对应于对象的行为( 或方法 )。

一、方法的基本定义

方法( method )用来实现类的行为。一个方法通常是用来完成一项具体的功能( function ),所以方法在 C/C++ 中也称为成员函数( member function )。英文 “ function ” 的这两层含义( 函数与功能 )在这里都能得到体现。

在 Java 语言中,每条指令的执行都是在某个特定方法的上下文中完成的。可把方法看成完成一定功能的 “ 黑盒 ”,方法的使用者( 对象 )只要将数据传递给方法体( 要么通过方法中的参数传递,要么通过对象中的数据成员共享 ),就能得到结果,而无需关注方法的具体实现细节。当我们需要改变对象的属性( 状态 )值时,就让对象去调用对应的方法,方法通过对数据成员( 实例变量 )一系列的操作,然后就再将操作的结果返回。

在 Java 中,方法定义在类中,它和类的成员属性( 数据成员 )一起构建一个完整的类。构成方法有四大要素。返回值类型、方法名称、参数、方法体。这是一种标准,在大多数编程语言中都是通用的。

所有方法均在类中定义和声明。一般情况下,定义一个方法的语法如下所示:

修饰符 返回值类型 方法名(参数列表)
        {
            //方法体
            return 返回值;
        }

方法包含一个方法头( method header )和一个方法体。方法头包括修饰符、返回值类型、方法名和参数列表等。下面一一给予解释:

修饰符( modifier ):定义了该方法的访问类型。这是可选的,它告诉编译器以什么调用该方法。

返回值类型( return type ):指定了方法返回的数据类型。它可以是任意有效的类型,包括构造类型( 类就是一种构造类型 )。如果方法没有返回值,则其返回类型必须是 void。方法体重的返回值类型要与方法头中定义的返回值类型一致。

方法名( method name ):方法名称的命名规则遵循 Java 标识符命名规范,但通常方法名以英文中的动词开头。这个名字可以是任意合法标识符。

参数列表( parameter list ):参数列表是由类型、标识符组成的序列,每对之间用逗号分开。参数实际上是方法被调用时接收传递过来的参数值的变量,如果方法没有参数,那么参数表为空的,但是圆括号不能省略。参数列表可将该方法需要的一些必要的数据传给该方法。方法名和参数列表共同构成方法签名,一起来标识方法的身份信息。

方法体( body ):方法体中存放的是封装在 {  } 内部的逻辑语句,用以完成一定的功能。

方法( 或称函数 )在任何一种编程语言中都很重要。它们的实现方式大同小异。方法是对逻辑代码的封装,使程序结构完整条理清晰,便于后期的维护和扩展。面向对象的编程语言将这一特点进一步放大,我们可以通过对方法加以权限修饰( 如 private、public、protected 等 )来控制方法能够在任何处被调用。灵活的运用方法和权限修饰符对编码的逻辑控制非常有帮助。

二、方法的使用

class Person
        {
            String name;    //没有封装
            int age;    //没有封装

            void talk()
            {
        System.out.println("我是:" + name + “,今年:” + age + “岁”);
            }

            void setName(String name)
            {
                this.name = name;
            }

            void setAge(int age)
            {
                this.age = age;
            }
        }
    
        public class PersonTest
        {
            public static void main(String[] args)
            {
                Person p1 = new Person();
                p1.setName("张三");
                p1.setAge(24);
                p1.talk();
            }
        }

这样看来似乎更加便捷,但是却违背了面向独享程序设计的一个重要原则——数据隐藏( data hiding ),也就是封装性。这里只供参考。

三、方法中的形参与实参

如果有传递消息的需要,在定义一个方法时,参数列表中的参数个数至少为 1 个,有了这样的参数才有提供外部传递消息至本方法的可能。这些参数被称为形式参数,简称形参( parameter )。而在调用这个方法时,需要调用者提供与原方法定义相匹配的参数( 类型、数量及顺序都一致 ),这些实际调用时提供的参数称为实际参数,简称实参( argument )。

例如:

int max(int num1,int num2)    //参数num1,num2为形参

        n.max(a,b)    //参数a,参数b为实参

形参和实参的关系如下:

1)形参变量隶属于方法体,也就是说他们是方法的局部变量,只当在被调用时才被创建,才被临时性的分配内存,在调用结束后,立即释放所分配的内存单元。也就是说,当方法调用返回后,就不能再使用这些形式参数。

2)在调用方法时,实参和形参在数量上、类型上、顺序上应严格保证一一对应的关系,否则就会出现参数类型不匹配的错误,从而导致调用方法失败。

四、方法的重载

方法的名称及其参数列表( 参数类型 + 参数个数 )一起构成方法的签名( method signature )。就如同人们在正式文书上通过签名来区分不同人一样,编译器也可通过不同的方法签名来区分不同的方法。这种使用方法名相同但参数列表不同的签名机制,称之为方法的重载( method overload )。在调用的时候,编译器会根据参数的类型或个数不同来执行不同的方法体代码。

例如:

import java.lang.String;
        public class OverloadValueOf
        {
            public static void main(String args[]){
        
                byte num_byte = 1;
                short num_short = 22;
                int num_int = 345;
                float num_float = 67.89f;
                boolean b_value = false;

                System.out.pringln("Value of num_byte is" + String.valueOf(num_byte));
                System.out.pringln("Value of num_short is" + String.valueOf(num_short));
                System.out.pringln("Value of num_int is" + String.valueOf(num_int));
                System.out.pringln("Value of num_float is" + String.valueOf(num_float));
                System.out.pringln("Value of b_value is" + String.valueOf(b_value));

            }
        }

在自定义设计重载方法时,需要注意以下 3 点,这些重载方法之间:

(1)方法名称相同。

(2)方法的参数列表不同( 参数个数、参数类型、参数顺序,至少有一项不同 )。

(3)方法的返回值类型和修饰符不作要求,可以相同,也可以不同。

Java 方法重载是通过方法的参数列表的不同来加以区分实现的。

提示:方法的签名仅包括方法名称和参数,因此方法重载不能根据方法的不同返回值来区分不同方法,因为返回值不属于方法签名的一部分。例如,int add(int,int) 和 void add(int,int) 的方法签名是相同的,编译器会 “ 认为 ” 这两个方法完全相同而无法区分,故此它们无法达到重载的目的。

方法重载是在 Java 中随处可见的特性,与之类似的还有 “ 方法覆盖 ”,该特性是基于 “ 继承 ” 的。

五、构造方法

“ 构造 ” 一词来自于英文 “ Constructor ”,中文常译为 “ 构造器 ”,又称为构造函数( C++中 )或构造方法( Java中 )。构造方法与普通方法的差别在于,它是专用于在构造对象时初始对象成员的,其名称和其所属类名相同。

1.构造方法

声明对象并实例化的格式为:

类名称 对象名称 = new 类名称();

下面分别来观察这一步的 4 层作用。

(1)类名称:表示要定义变量的类型,只是有了类之后,变量的类型是由用户自己定义的。

(2)对象名称:表示变量的名称,变量的命名规范与方法相同。

(3)new :是作为开辟堆内存的唯一方法,表示实例化对象。

(4)类名称( ):这就是一个构造方法。

所谓构造方法,就是在每一个类中定义的,并且是在使用关键字 new 实例化一个新对象的时候默认调用的方法。在 Java程序里,构造方法所完成的主要工作是对新创建对象的数据成员赋初值。可将构造方法视为一种特殊的方法,其定义方式如下:

class 类名称
        {
            访问权限 类名称(类型1  参数1,类型2  参数2,…… )    //构造方法
            {
                程序语句;
                ……//构造方法没有返回值
            }
        }

在使用构造方法的时候需注意以下几点。

(1)构造方法名称和其所属的类名必须保持一致。

(2)构造方法没有返回值,也不可以使用 void。

(3)构造方法也可以像普通方法一样被重载。

(4)构造方法不能被 static 和 final 修饰。

(5)构造方法不能被继承,子类使用父类的构造方法需要使用 super 关键字。

构造方法除了没有返回值,且名称必须与类的名称相同之外,它的调用时机也与普通方法有所不同。普通方法是在需要时才调用,而构造方法则是在创建对象时就自动 “ 隐式 ” 执行。因此,构造方法无需再程序中直接调用,而是在对象产生时自动执行一次。通常它用来对对象的数据成员进行初始化。

例如:

public class showConstruct
        {
            public static void main(String[] args)
            {
                Person p = new Person(1);
                p.show("Java 构造方法的使用演示!");
            }
        }

        class Person
        {
            public Person(int x)
            {
                a = x;    //用构造方法的参数x来初始化私有变量a
                System.out.println("构造方法被调用……");
                System.out.println("a = " + a);
            }

            public void show(String msg)
            {
                System.out.println(msg);
            }

            private int a;
        }

在类中声明的构造方法,会在实例化对象时自动调用且只被调用一次。如果在类中并没有声明任何构造方法,也可以运行。这是因为在执行 javac 编译 java 程序的时候,如果在程序中没有明确声明一个构造方法的话,编译器会自动为该类添加一个无参数的构造方法。

这样一来,就可以保证每一个类中至少存在一个构造方法( 也可以说没有构造方法的类是不存在的 ),所以如果没有声明任何构造方法,程序也可以是正常运行的。

提示:对于一个构造方法 public Book( ) { } 和一个普通方法 public void Book( ) { },二者的区别在于,如果构造方法上写上 void,那么其定义的形式就与普通方法一样了。构造方法是在一个对象实例化的时候只调用一次的方法,而普通方法则可通过一个实例化对象调用多次。正是因为构造方法的特殊性,它才有特殊的语法规范。

2.构造方法的重载

在 Java 里,普通方法是可以重载的,而构造方法在本质上也是方法的一种特例而已,因此它也可以重载。构造方法的名称是固定的,它们必须和类名保持一致,那么构造方法的重载,自然要体现参数列表的不同。也就是说,多个重载的构造方法彼此之间,参数个数、参数类型和参数顺序至少有一项是不同的。只要构造方法满足上述条件,便可定义多个名称相同的构造方法。

构造方法的基本功能就是对类中的属性初始化,在程序产生了类的实例对象时,将需要的参数由构造方法传入,之后再由构造方法为其内部的属性进行初始化,这是在一般开发中经常使用的技巧。

在 Java 程序中一旦用户显式声明了构造方法,那么默认的 “ 隐式 ” 构造方法就不会被编译器生成。而要解决这一问题,只需要在类中明确地声明一个无参数的构造方法。

3.构造方法的私有化

一个方法可根据实际需要,将其设置为不同的访问权限——public( 公有访问 )、private( 私有访问 )或默认访问( 即方法前没有修饰符 )。同样,构造方法也有 public 与 private 之分。使用了 public 的构造方法可以在程序的任何地方被调用,所以新创建的对象也都可以自动调用它。如果构造方法被设为 private,那么其他类中就无法调用该构造方法。换句话说,在本类之外,就不能通过 new 关键字调用该构造方法创建该类的实例化对象。

为什么要将构造方法私有化?事实上,构造方法虽然被私有了,但并不一定说此类不能产生实例化对象,只是产生这个实例化对象的位置有所变化,即只能在私有构造方法所属类中产生实例化对象。

例如:

public class PrivateConstructor
        {
            private PrivateConstructor()
            {
                System.out.println("Private Constructor \n 构造方法已被私有化!");
            }

            public static void main(String[] args)
            {
                new PrivateConstructor();
            }
        }

我们知道,实例化对象需要调用构造方法,但如果将构造方法使用 private 藏起来,则外部肯定无法直接调用,那么实例化该类对象就只能有一种途径——在该类内部用 new 关键字创建该类的实例。通过这个方式,我们就可以确保一个类只能创建一个实例化对象。在软件工程中,这种设计模式被称之为单态设计模式( Singleton Design Pattern )。

许多时候整个系统只需要拥有一个全局对象,这样有利于协调系统整体的行为。例如,在 Windows 中的回收站就是所有逻辑盘共享一个回收站,这是一个典型的单例模式设计。Java 中的构造方法私有化就是为这种软件设计模式而服务的。

也就是说,此类只能产生一个实例对象。这种做法就是单态设计模式。所谓设计模式也就是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。

六、在方法内部调用

class Person
        {
            private String name;
            private int age;
            private void talk()
            {
                System.out.println("我是:" + name + "今年:" + age + "岁");
            }

            public void say()
            {
                talk();
            }

            public String getName()
            {
                return name;
            }

            public void setName(String name)
            {
                this.name = name;
            }

            public int getAge()
            {
                return age;
            }

            public void setAge(int age)
            {
                this.age = age;
            }
        }

        public class TestPerson
        {
            public static void main(String[] args)
            {
                //声明并实例化一个Person对象p
                Person p = new Person();
                //给p中的属性赋值
                p.setName("张三");
                //在这里将p对象中的年龄属性赋值为22岁
                p.setAge(22);
                //调用Person类中的say()方法
                p.say();
            }
        }

在倒数第三行,调用 Person 类中的公有方法 say(),本质上,通过 say 方法调用了 Person 类中的私有方法 talk()。如果某些方法不方便公开,就可以用这种二次包装的模式来屏蔽不想公开的实现细节( 例如此处的talk() ),这在某些应用背景下是有需求的。

七、方法的递归(Recursion)调用

在程序设计领域,递归是指函数( 或方法 )直接或间接调用自身的一种操作。递归调用能够大大减少代码量,将原本复杂的问题简化成一个简单的基础操作来完成。在编码过程中 “ 递归调用 ” 是一个非常实用的技巧。

递归函数( 或方法 )不论是直接调用自身,还是间接调用自身,都是一种无终止的过程。因此,在编写递归代码时,递归程序一定要有结束条件,这又被称作递归出口。如果一个递归函数缺少递归出口,执行时就会陷入死循环。递归出口可用 if 语句来控制,在满足某种条件时继续递归调用自身,否则就不再继续。

例如:

public class RecursionMethod
        {
            //非递归方式实现计算“1+2+3+……+n”
            public int addNonrecursion(int n)
            {
                int result = 0;
                for(int i = 1;i<=n;i++)
                {
                    result += i;
                }
                return result;
            }

            //递归方式实现计算“1+2+3+……+n”
            public int addRecursion(int n)
            {
                //递归出口,当n小于1时函数就会逐层返回
                if(n<1)    return n;
                return n + addRecursion(n-1);
            }

            public static void main(String[] args)
            {
                RecursionMethod test = new RecursionMethod();
                //调用非递归方法计算
                int result = test.addNonrecursion(10);
                System.out.println("非递归计算 1+2+3+……+10 结果:" + result);
                //调用递归方法计算
                result = test.addRecursion(10);
                System.out.println("递归计算 1+2+3+……+10 结果:" + result);
            }
        }

递归通过对方法本身的压栈和弹栈的方式,将每一层的结果逐级返回,通过逐步累加求得结果。

虽然递归操作有许多的优点,但是缺点也很明显。使用递归实现需要函数压栈和弹栈的操作,所以程序的运行速度比不用递归实现要慢得多。

八、代码块

代码块是一种常见的代码形式。它用大括号 “ {  } ” 将多行代码封装在一起,形成一个独立的代码区域,这就构成了代码块。代码块的格式如下:

{
            //代码块
        }

代码块有四种形式:

1.普通代码块。

2.构造代码块。

3.静态代码块。

4.同步代码块。

代码块不能够独立运行,需要依赖与其他配置。

(1)普通代码块

普通代码块是最常见的代码块。它的形式是,在方法名后( 或方法体内 )用一对 “ {  } ” 括起来的数据块就是普通代码块。它不能够单独存在于类中,需要紧跟在方法名后面,并通过方法调用。

(2)构造代码块

构造代码块就是在类中直接定义的,且没有任何前缀、后缀以及修饰符的代码块。在一个类中,至少需要有一个构造方法( 如果用户自己不显示定义,编译器会 “ 隐式 ” 地配备一个 ),它在生成对象是被调用。构造代码块和构造方法一样是在对象生成时被调用,但是它的调用时机比构造方法还要早。

由于这种特性,构造代码块可用来初始化成员变量。如果一个类中有多个构造方法,这些构造方法都需要初始化成员变量,那么就可以把每个构造方法中相同的代码部分抽取出来,集中一起放在构造代码块中。这样利用构造代码块来初始化共有的成员变量,可大大减少不同构造方法中重复的代码,提高代码的复用性。

构造代码块不在任何方法之内,仅位于类的范围内,它的地位和其他方法体是对等的,可以理解为构造代码酷开是没有名称的方法体,但仅限用与对类数据成员的初始化,且仅运行一次。

在类被实例化的过程中,构造代码块内的代码比构造方法先执行。构造代码不仅可以减少代码量,也提高了代码的可读性,善用构造代码块能够给编码带来许多便利。

(3)静态代码块

使用 static 关键字加以修饰并用大括号 “ {  } ” 括起来的代码块称为静态代码块,其主要用来初始化静态成员变量。它是最早执行的代码块。

例如:

public class StaticCodeBlock
            {
            //静态代码块
            static
            {
                System.out.println("静态代码块执行……");
            }

            //构造方法
            public StaticCodeBlock()
            {
                System.out.println("构造方法执行……");
            }

            //构造代码块
            {
                System.out.println("构造代码块执行……");
            }

            public static void main(String[] args)
            {
                System.out.println("main方法开始执行……");
                System.out.println("创建第1个对象……");
                new StaticCodeBlock();
                System.out.println("创建第2个对象……");
                new StaticCodeBlock();
                System.out.println("创建第3个对象……");
                new StaticCodeBlock();
            }
        }

静态代码块的执行时间比主方法 main() 方法都要早。静态块还优先于构造方法的执行,而且不管有多少个实例化对象产生,静态块都只执行一次。利用这种特性,静态代码块可以被用来初始化类中的静态成员变量。静态成员变量是属于所有类对象共享的,故此不会受到创建对象个数的影响。

在执行时机上,静态代码块在类加载时就会执行,因此早于构造代码块、构造方法。当静态代码块和 main 方法属于一个类时,静态代码块比 main 方法执行早。静态块的执行级别是最高的。

(4)同步代码块

如果在代码块前加上 synchronized 关键字,则此代码块就成为同步代码块。同步代码块的格式如下:

synchronized(同步对象){
            //需要同步的代码;
        }

同步对象一般为当前对象,即使用 this 关键字。

九、方法与数组

数组也是常见的一种数据结构,几乎所有的编程语言都支持。在 Java 数组中存放的是一组类型相同的数据序列,并通过下标对数组元素进行访问。在基本数据类型构成的数组的基础上,抽象出了数组对象,整合了一系列具有相同特点的数据。再进一步地扩展,便构成功能更强大的 “ 集合 ”。从本质上来说,它们都是存储数据的容器( container )。与存储单个数据的变量相比较,它们存储的是多个数据,或是多组数组( 如 map 集合等 )。

1.数组引用传递

抽象的概念都源于具体的表象。在现实生活中,“ 引用(reference)” 的例子也很多,例如,中国自主研制的 CPU——龙芯,小名狗剩,外文名 GodSon( 后又改名为Loongson ),它们三者名称虽然不同,但指向的都是中国自主研制的 CPU。

一个程序若想运行,必须驻入内存,而在内存中必然有其存储地址,通过这些内存地址,就可以找到我们想要的数据。这些内存地址通常都很长( 具体长度取决于 JVM 的类型 ),因为不容易记住,所以就给这些地址取个名称,这就是引用变量,这些引用变量存储在一块名叫 “ 堆内存 ” 的区域。

那么所谓 “ 引用 ”,就是 Java 对象在堆内存的地址赋给了多个 “ 栈内存 ” 的变量,由于 Java 禁止用户直接操作 “ 堆整型、浮点型、布尔型等基本数据类内存 ” 中对象的地址,所以只能用这些 “ 栈内存 ” 的多个引用名来简介操作它们对应的 “ 堆内存 ” 数据。所以,Java 中的 “ 引用 ” 更类似于 C/C++ 中的 “ 指针 ” 概念,所不同的是,C/C++ 中的 “ 指针 ” 可以被用户直接修改,而在 Java 中对内存的直接修改是被屏蔽的。

在 Java 中,所有对象都是通过引用进行操作的。而数组也是一种对象。当将数组作为参数传递给方法时,传递的实际上就是该数组对象的引用。在方法中对数组的所有操作,都会映射到原数组中,这一点也是 Java 面向对象的一个重要特点。

所谓的数组引用传递,就是将一块数组的堆内存空间交由多个占内存所指向。这包括了方法通过参数列表接收数组和方法计算完毕后返回数组等两种情况,但不管数组操作个形势如何改变,最终都属于引用传递。请注意,仅有引用型数据类型具备盖特性,整型、浮点型、布尔型等基本数据类型都不具备该特性。

例如:

public class ArrayReference
        {
            public static void changeReferValue(int a,int[] myArr)
            {
                a+=1;    //将参数a加1
                myArr[0] = 0;    //将数组的三个元素全部归0
                myArr[1] = 0;
                myArr[2] = 0;
            }

            //打印数组元素
            public static void printArr(int[] arr)
            {
                for(int i:arr)
                {
                    System.out.print(i + " ");
                }
                System.out.println();    //输出一个换行符
            }
            //打印结果
            public static void print(int in,int[] arr)
            {
                System.out.println("in:" + in);
                System.out.print("arr:");
                printArr(arr);
            }

            public static void main(String[] args)
            {
                int in = 10;
                int arr[] = {1,2,3,4,5};
                System.out.println("————调用changeReferValue方法之前————");
                print(in,arr);

                changeReferValue(in,arr);
                System.out.println("————调用changeReferValue方法之后————");
                print(in,arr);
            }
        }

在 Java1.5 以后的版本中,对于数组和集合框架( Collection )等类型的对象,提供了一种新的遍历方式,称为 for-each,结构如下:

for(循环变量类型 循环变量名称:要被遍历的对象)
        {
            //循环体
        }

Java 集合框架是由一套设计优良的接口和类组成的,可使程序员成批地操作数据或对象元素。

也可替换成传统的 for 循环方式:

for(int i = 0;i<arr.length;i++)
        {
            System.out.print(arr[i] + " ");
        }

因为数组是对象,所以在 changeReferValue 方法的参数列表中,最后一个参数传递形式为 “ 传引用 ”。换句话说,main 方法中实参 arr 和 changeReferValue 方法中的形参 myArr 指向同一块内存。在 changeReferValue 方法中,由于对于整型形参 a 和 实参 in 之间是 “ 传值 ” 关系。在实参 in 将值赋值给形参 a 后,形参和实参二者之间再没有任何关联,所以在方法 changeReferValue 中对 a 的 +1 操作,并没有影响实参 in 的值,在本质上,形参 a 和实参 in 所指向的完全是不同的内存空间。

2.在方法中实现排序

排序算法有很多,比较著名的有快速排序算法、冒泡排序算法、选择排序算法、归并排序算法等。每种算法都有优缺点,在合适的场景选择合适的算法能够提高程序的性能。

举个冒泡排序的例子:

public class ArraySort
        {
            public static void sort(int[] arr)
            {
                //冒泡排序算法
                for(int i=0;i<arr.length;i++)
                {
                    for(int j=i+1;j<arr.length;j++)
                    {
                        if(arr[i]<arr[j])
                        {
                            int temp = arr[i];
                            arr[i] = arr[j];
                            arr[j] = temp;
                        }
                    }
                 }
            }

            public static void printArr(int[] arr,String msg)
            {
                System.out.pringln(msg);
                for(int i:arr)
                {
                    System.out.print(i + " ");
                }
                System.out.println();
            }

            public static void main(String[] args)
            {
                int[] arr = {3,4,5,6,7,8,1};
                printArr(arr,"排序前:");
                sort(arr);
                printArr(arr,"排序后:");
            }
        }

由于 sort 方法的参数传递方式数组对象的引用,故此,在 sort 方法体对数组 arr 的所有修改,都会在主函数定义的数组对象 arr 中体现出来,所以 sort 函数不需要返回值。在 sort 方法中,数组用使用 “ 传引用 ” 方式,保证了 sort 内的数组 arr 和 main 中的数组 arr 本质上同一个数组( 它们的引用指向的是同一块内存空间 ),这样在 sort 方法中完成排序后,自然不用 “ 多此一举 ” 的将其排序后的结果返回 main 方法了。

3.让方法返回数组

方法的返回值可以是 Java 所支持的任意一种类型。数组作为对象同样也可以成为方法的返回值。

例如:

public class ArrReturn
        {
            public static int[] sort(int[] arr)
            {
                //冒泡排序算法
                for(int i=0;i<arr.length;i++)
                {
                    for(int j=i+1;j<arr.length;j++)
                    {
                        if(arr[i]<arr[j])
                        {
                            int temp = arr[i];
                            arr[i] = arr[j];
                            arr[j] = temp;
                        }
                    }
                }
                return arr;
            }

            public static void printArr(int[] arr,String msg)
            {
                System.out.pringln(msg);
                for(int i:arr)
                {
                    System.out.print(i + " ");
                }
                System.out.println();
            }

            public static void main(String[] args)
            {
                int[] arr = {3,4,5,6,7,8,1};
                //创建一个新数组引用
                int[] arrnew;
                printArr(arr,"排序前:");
                //用新引用接收排序结果
                arrnew = sort(arr);
                printArr(arr,"排序后:");
            }
        }

实际上,数组对象引用的核心功能没有改变,它们都是一块堆内存设置了多个栈内存指向——一个数组对象( 存储于堆内存中 )对应多个别名( 引用,存在于堆内存中 )。

十、与数组有关的操作方法

Java 对数组进行了封装和抽象,实现了 Array 接口。一些框架集合( collection )继承了接口,对数组的功能进行了扩展,形成了一类功能强大的工具集。在 Java 中,针对数组操作的支持主要有两个,数组的克隆和数组的排序。

1.数组的克隆

由基本数据类型构建的数组,如 int[]、double[] 等,Java 提供的内置方法并不是很多,最常用的方法是 clone() 方法,它会将数组复制一份,返回一个新的引用,常用来复制数组。数组对象提供的 length 属性用于记录数组的长度,即数组中包含元素的个数。

例如:

public class ArrayMethod
        {
            public static void printArr(int[] arr)
            {
                for(int i:arr)
                {
                    System.out.print(i + " ");
                }
                System.out.println();    //输出一个换行符
            }
    
            public static void main(String[] args)
            {
                int[] arr = {2,3,4,5,6,7,8,9,1};
                int[] arrnew = arr.clone();    //arrnew克隆arr的数组元素
                System.out.print("arr:");
                printArr(arr);
                System.out.println("arr.length:" + arr.length);    //输出数组的长度
                System.out.println("————————————————————————");
                System.out.println("arrnew:");
                printArr(arrnew);
                System.out.println("arrnew.length:" + arrnew.length);    //输出数组的长度
    
                if(arr == arrnew)
                    System.out.println("数组arr和数组arrnew的引用地址相同!");
                else
                    System.out.println("数组arr和数组arrnew的引用地址不同!");
            }
        }

通过 “ 克隆 ” 机制,数组 arrnew 在堆内存中另辟一块和数组 arr 等大的内存,然后一一复制 arr 中的元素,克隆完毕后,两个数组中的元素值是一一对应相同的。由于 arr 和 newarr 对应两块不同的内存空间,克隆之后,它们各自元素的修改,不会对另外一个数组产生任何影响。

Java 的所有类都默认继承 java.lang.Object 类,在 java.lang.Object 类中有一个办法 clone()。这个办法将返回 Object 对象的一个拷贝。这里有两点需要特别说明:

(1)“ 克隆 ”( 拷贝 )对象返回的是一个新对象,而不是一个已有对象的引用。

(2)“ 克隆 ”( 拷贝 )对象与用 new 操作符返回的新对象是有区别的,克隆对象是拷贝某个对象的当前信息,而不是对象的初始信息。

2.数组的排序

事实上,Java 提供了大量的成熟的开发库,基于这些开发包库,合力的使用它们能极大地提高我们的开发效率。

例如:

public class ArraySortUsingPackage
        {
            public static void printArr(int[] arr,String msg)
            {
                System.out.println(msg);
                for(int i:arr)
                {
                    System.out.print(i + " ");
                }
                System.out.println();
            }

            public static void main(String[] args)
            {
                int[] arr = {3,4,5,6,7,8,1};
                printArr(arr,"排序前:");
                java.util.Arrays.sort(arr);    //利用 Java 的包库提供的方法来排序
                printArr(arr,"排序后:");
            }
        }

不同的是,我们没有自定义数组元素的排序算法,而是使用了 Java 提供的 sort 方法。

Java 对数组的排序方法 sort(),存在于 util.Arrays 的包中,我们要明确的指定 sort 方法的来源。

java.util.Arrays.sort(arr);

或者在代码的第一行,导入 java.util 库包。

import java.util.*;

然后在使用简洁的调用方式。

Arrays.sort(arr);

Arrays.sort() 提供的默认排序方式是升序。

十一、本文注意事项

1.Eclipse 中自动生成 Getter 和 Setter 方法

在代码的空白处单击右键→【source】【Generate Getters and Setters】,然后将想要生成的属性勾选上,或直接单击【Select All】全选属性,单击【OK】即可。

快捷键为:Alt + Shift + S + R

2.Eclipse 中自动生成构造方法( 构造函数 )

Eclipse 还可以辅助用户自动生成构造方法,步骤同生成 Getter 方法和 Setter 方法类似。

在代码的空白处单击右键→【source】【Generate Constructor using fields】,然后将想要生成的属性勾选上,或直接单击【Select All】全选属性,单击【OK】即可。

快捷键为:Alt + Shift + S,弹出对话框后找到【Generate Constructor using fields】即可。