java面向对象(类和对象、方法详解)

104 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

1 类和对象

1.1 定义类

static修饰的成员不能访问没有static修饰的成员。

如果程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器。 一旦程序员为一个类提供了构造器,系统将不再为该类提供构造器。

static的真正作用就是用于区分Field、方法、内部类、初始化块这四种成员到底属于类本身还是属于实例。在类中定义的成员,有static修饰的成员属于类本身,没有static修饰的成员属于该类的实例。

值得指出的是,构造器既不能定义返回值类型,也不能使用void定义构造器没有返回值。如果为构造器定义了返回值类型,或使用void声明构造器没有返回值,编译时不会出错,但Java会把这个所谓的构造器当成方法来处理。

1.2 对象的产生和使用

1.3 对象、引用和指针

1.4 对象的this引用

this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的对象只能是当前类;只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this就代表谁

Java有一个让人极易“混淆”的语法,它允许使用对象来调用static修饰的Field、方法,但实际上这是不应该的 Java编程时不要使用对象去调用static修饰的Field、方法,而是应该使用类去调用static修饰的Field、方法

普通方法访问其他方法、Field时无须使用this前缀,但如果方法里有个局部变量和Field同名,但程序又需要在该方法里访问这个被覆盖的Field,则必须使用this前缀。

除此之外,this引用也可以用于构造器中作为默认引用,由于构造器是直接使用new关键字来调用,而不是使用对象来调用的,所以this在构造器中引用的是该构造器进行初始化的对象

2 方法详解

2.1 方法的所属性

同一个类的一个方法调用另外一个方法时,如果被调方法是普通方法,则默认使用this作为调用者;如果被调方法是静态方法,则默认使用类作为调用者。也就是说,表面上看起来某些方法可以被独立执行,但实际上还是使用this或者类来作为调用者

使用static修饰的方法属于这个类本身,使用static修饰的方法既可以使用类作为调用者来调用,也可以使用对象作为调用者来调用。但值得指出的是,因为使用static修饰的方法还是属于这个类的,因此使用该类的任何对象来调用这个方法时将会得到相同的执行结果,因为实际上还是使用这些实例所属的类作为调用者。

没有static修饰的方法则属于该类的对象,不属于这个类本身。因此没有static修饰的方法只能使用对象作为调用者调用,不能使用类作为调用者调用。使用不同对象作为调用者来调用同一个普通方法,可能得到不同的结果。

2.2 方法的参数传递机制

阅读下面程序,程序输出结果为?

        public class PrimitiveTransferTest
        {
            public static void swap(int a, int b)
            {
                  //下面三行代码实现a、b变量的值交换
                  //定义一个临时变量来保存a变量的值
                  int tmp=a;
                  //把b的值赋给a
                  a=b;
                  //把临时变量tmp的值赋给a
                  b=tmp;
                  System.out.println("swap方法里,a的值是"
                        + a + ";b的值是" + b);
            }
            public static void main(String[] args)
            {
                  int a=6;
                  int b=9;
                  swap(a, b);
                  System.out.println("交换结束后,变量a的值是"
                        + a + ";变量b的值是" + b);
            }
        }

答:a、b的值没有变,为什么呢? 因为main方法里的变量a和b,并不是swap方法里的a和b。swap方法的a和b只是main方法里变量a和b的复制品

基本类型的参数传递,Java对于引用类型的参数传递,一样采用的是值传递方式

        class DataWrap
        {
            public int a;
            public int b;
        }
        public class ReferenceTransferTest
        {
            public static void swap(DataWrap dw)
            {
                  //下面三行代码实现dw的a、b两个Field值交换
                  //定义一个临时变量来保存dw对象的a Field的值
                  int tmp=dw.a;
                  //把dw对象的b Field的值赋给a Field
                  dw.a=dw.b;
                  //把临时变量tmp的值赋给dw对象的b Field
                  dw.b=tmp;
                  System.out.println("swap方法里,a Field的值是"
                        + dw.a + ";b Field的值是" + dw.b);
            }
            public static void main(String[] args)
            {
                  DataWrap dw=new DataWrap();
                  dw.a=6;
                  dw.b=9;
                  swap(dw);
                  System.out.println("交换结束后,a Field的值是"
                        + dw.a + ";b Field的值是" + dw.b);
            }
        }

执行上面程序,看到如下运行结果: swap方法里,a Field的值是9;b Field的值是6 交换结束后,a Field的值是9;b Field的值是6

这种参数传递方式是不折不扣的值传递方式,系统一样复制了dw的副本传入swap方法,但关键在于dw只是一个引用变量,所以系统复制了dw变量,但并未复制DataWrap对象。 main方法中的dw传入swap方法后存储示意图

2.3 形参个数可变的方法

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

        public class Varargs
        {
            //定义了形参个数可变的方法
            public static void test(int a , String... books)
            {
                  //books被当成数组处理
                  for (String tmp : books)
                  {
                        System.out.println(tmp);
                  }
                  //输出整数变量a的值
                  System.out.println(a);
            }
            public static void main(String[] args)
            {
                  //调用test方法
                  test(5 , "疯狂Java讲义" , "轻量级Java EE企业应用实战");
            }
        }

从test的方法体代码来看,形参个数可变的参数其实就是一个数组参数,也就是说,下面两个方法签名的效果完全一样

        //以可变个数形参来定义方法
        public static void test(int a , String... books);
        //下面采用数组形参来定义方法
        public static void test(int a , String[] books);

注意: 长度可变的形参只能处于形参列表的最后。一个方法中最多只能包含一个长度可变的形参。调用包含一个长度可变形参的方法时,这个长度可变的形参既可以传入多个参数,也可以传入一个数组。

2.4 方法重载

Java允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。

方法重载的要求就是两同一不同同一个类方法名相同参数列表不同。至于方法的其他部分,如方法返回值类型、修饰符等,与方法重载没有任何关系。

为什么方法的返回值类型不能用于区分重载的方法? 答:对于int f(){}和void f(){}两个方法,如果这样调用int result=f();,系统可以识别是调用返回值类型为int的方法;但Java调用方法时可以忽略方法返回值,如果采用如下方法来调用f();,你能判断是调用哪个方法吗?如果你尚且不能判断,那么Java系统也会糊涂。在编程过程中有一条重要规则:不要让系统糊涂,系统一糊涂,肯定就是你错了。因此,Java里不能使用方法返回值类型作为区分方法重载的依据。

不仅如此,如果被重载的方法里包含了长度可变的形参,则需要注意。看下面程序里定义的两个重载的方法。

        public class OverloadVarargs
        {
            public void test(String msg)
            {
                  System.out.println("只有一个字符串参数的test方法 ");
            }
            //因为前面已经有了一个test方法,test方法里有一个字符串参数
            //此处的长度可变形参里不包含一个字符串参数的形式
            public void test(String... books)
            {
                  System.out.println("****形参长度可变的test方法****");
            }
            public static void main(String[] args)
            {
                  OverloadVarargs olv=new OverloadVarargs();
                  //下面两次调用将执行第二个test方法
                  olv.test();
                  olv.test("aa" , "bb");
                  //下面调用将执行第一个test方法
                  olv.test("aa");
                  //下面调用将执行第二个test方法
                  olv.test(new String[]{"aa"});
            }
        }

编译、运行上面程序,将看到olv.test();和olv.test("aa" , "bb");两次调用的是test(String... books)方法,而olv.test("aa");则调用的是test(String msg)方法。通过这个程序可以看出,如果同一个类中定义了test(String... books)方法,同时还定义了一个test(String)方法,则test(String... books)方法的books不可能通过直接传入一个字符串参数,如果只传入一个参数,系统会执行重载的test(String)方法。如果需要调用test(String... books)方法,又只想传入一个字符串参数,则可采用传入字符串数组的形式,如下代码所示。

        olv.test(new String[]{"aa"});

大部分时候,我们不推荐重载形参长度可变的方法,因为这样做确实没有太大的意义,而且容易降低程序的可读性。