Java类和对象、方法、成员变量和局部变量

参考资料

[1]. 疯狂Java讲义(第三版) 李刚

类和对象

定义类

类(class)和对象(object,也被称为实例,instanceof),其中类是某一批对象的抽象,可以把类理解成某种观念,对象才是一个具体的实体。
普通类或抽象类的语法如下:

[public] [final] [abstract] class 类名 [extends 被继承的类名,] implements 接口1, 接口2, ...
{
    零到一个初始化块或静态初始化块...
    零个到多个构造器定义...
    零到多个成员变量...
    零到多个方法...
}

定义成员变量的语法如下:

[public|protected|private] [static] [final] 类型 成员变量名称 [=默认值];

定义抽象方法的语法如下:

[public|protected|private]   abstract   返回类型   类名( [形参列表] );

定义普通方法的语法如下:

[public|protected|private]  [static] [final] [返回类型|void]    类名( [形参列表] )
{
...
}

定义构造器的语法如下:

[public|protected|private]   构造器名   ( [形参列表] )
{
...
}

对象的产生和使用

声明一个Person类

/**
 * 测试类
 */
public class Person {
    /**
     * 成员变量1
     */
    public String name;
    /**
     * 成员变量2
     */
    public int age;

    /**
     * say方法
     * @param content
     */
    public void say(String content)
    {
        System.out.println(content);
    }
    /**
     * 静态talk方法
     * @param content
     */
    public static void talk(String content)
    {
        System.out.println(content);
    }
}

声明一个类

// 声明一个Person类型的变量
Person p;
// 实例化一个Person类赋值给p
p = new Person();

使用一个类

// 使用静态类
Person.talk("talk");
// 使用非静态类
p = new Person();
p.name = "小a";
p.say("say");

对象、引用和指针

// 使用非静态类
p = new Person();
p.name = "小a";
p.say("say");

p变量被存到栈内存里了,它指向实际的Person对象,真正的Person对象则放在堆(heap)内存中,不管是数组还是对象,都只能通过引用来访问它们,比如变量p,如下图。
这里写图片描述
多个引用共享一个堆(heap)内存,代码如下

Person p;
p = new Person();
// 引用一个p2变量
Person p2 = p;
p.name = "小a";
// 当输出p2的name属性时可以看到它已经是"小a"
System.out.println(p2.name);

如果希望通知系统回收该对象,只需切断该对象的所有引用变量即可,可以使用null。

Person p;
p = new Person();
// 通知系统回收该对象
p = null;

对象的this引用

this关键字最大的作用就是让类中的一个方法,访问该类的另一个类方法或实例变量。

package com.qunar.c09;

public class Dog {
    /**
     * name
     */
    String name = "dog1";
    /**
     * age
     */
    Integer age = 3;

    /**
     * 构造器
     */
    public Dog()
    {
        // 设置属性
        System.out.println(this.name);
    }

    /**
     * 年龄增长
     * @return
     */
    public Dog ageAdd()
    {
        this.age ++;
        // 返回一个Dog对象
        return this;
    }
    /**
     * jum方法
     */
    public void jump()
    {
        System.out.println("正在执行jump方法");
    }

    /**
     * 定义一个run方法
     */
    public void run()
    {
        // 调用jump方法
        this.jump();
        // 也可以省略this
        // jump();
        System.out.println("正在执行run方法");
    }
}

当在某个方法中把this作为返回值,则可以连续调用同一个方法

Dog d1 = new Dog();
Dog d2 = d1.ageAdd().ageAdd().ageAdd();
System.out.println(d2.age);

方法详解

方法的所属性

Java语言里方法的所属性主要体现在下面几个方面:
1. 方法不能独立定义,方法只能在类体里定义。
2. 从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象。
3. 永远不能独立执行方法,执行方法必须使用类或对象作为调用者。

方法参数的传递机制

声明方法时包含了形参声明,在调用方法时必须给这些形参指定参数值,调用方法时实际传递给形参的参数值也被称为实参。
Java里方法的参数传递方式只有一种:值传递,即将实际参数值的副本(复制品)传入方法内,而参数本身不会受到影响。

public class PrimitiveTransferTest {

    public static  void swap(int a, int b)
    {
        int tmp = a;
        a = b;
        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);
    }
}

main()方法定义了a、b两个局部变量。
这里写图片描述
在swap()方法,系统将main()方法里的a、b局部变量传入,但只是a、b的副本,而不是a、b本身。
这里写图片描述
在这一步将局部变量a赋值给tem,并交换a和b的引用。
这里写图片描述
传入对象

/**
 * 交换数据对象
 */
public class DataWrap {
    int a;
    int b;
}

交换过程

public class ReferenceTransferTest {
    public static void swap(DataWrap dw)
    {
        int tmp = dw.a;
        dw.a = dw.b;
        dw.b = tmp;
        System.out.println("swap方法里,a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
    }

    public static void main(String[] args) {
        DataWrap dw = new DataWrap();
        dw.a = 6;
        dw.b = 9;
        swap(dw);
        System.out.println("交换结束后,a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
    }
}

程序从main()方法开始执行,main()方法开始创建了一个DataWrap对象,并定义了一个dw对象赋值。
这里写图片描述
将dw传入swap()方法,dw变量是一个指针,所以系统只复制了DataWrap的引用dw,在swap里面操作的也是dw对象,此时如下图:
这里写图片描述
如果在swap()方法里面添加代码dw = null; 以后只是让swap栈区里的dw引用失效,而main栈区里的dw引用不受影响。
这里写图片描述

形参个数可变的方法

从JDK1.5以后,Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三个点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。

public class Varargs {
    /**
     * 测试方法
     * @param a
     * @param letters
     */
    public static void test(int a, String ... letters)
    {
        // 遍历后面的String形参
        for (String letter : letters)
        {
            System.out.println(letter);
        }
        System.out.println(a);
    }

    public static void main(String[] args) {
        test(10, "a", "b", "c", "d");
    }
}

下面两个方法签名的效果完全一样:

public static void test(int a, String ... letters)
//  调用方法修改为test(10, new String[]{"a", "b", "c", "d"});
public static void test(int a, String[] letters)

数组形式的形参可以处于形参列表的任意位置,但个数可变的形参只能处于形参列表的最后,也就是说,一个方法中最多只能有一个长度可变的形参。

递归方法

在一个方法内调用它自身,被称为方法递归。方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种循环执行无须循环控制。
下面是一个递归方法实现的数列
f(0)=1,f(1)=4,f(n+2)=2f(n+1)+f(n) f ( 0 ) = 1 , f ( 1 ) = 4 , f ( n + 2 ) = 2 ∗ f ( n + 1 ) + f ( n )
其中 n n 是大于0 的整数,求f(10) 的值。
代码如下:

public class Recursive {
    public static int fn(int n)
    {
        if (n == 0)
        {
            return 1;
        }else if(n==1){
            return 4;
        }else{
            return 2 * fn(n - 1) + fn(n - 2);
        }
    }

    public static void main(String[] args) {
        // 执行递归
        System.out.println(fn(10));
    }
}

如果修改题为下面
f(20)=1,f(21)=4,f(n+2)=2f(n+1)+f(n) f ( 20 ) = 1 , f ( 21 ) = 4 , f ( n + 2 ) = 2 ∗ f ( n + 1 ) + f ( n )
其中 n n 是大于0 的整数,求 f(10) 的值,代码如下:

 public static int fn(int n)
    {
        if (n == 0)
        {
            return 1;
        }else if(n==1){
            return 4;
        }else{
            return  fn(n + 2) -2 * fn(n + 1);
        }
    }

方法重载

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

public class Overload {
    /**
     * 无参数test
     */
    public void test()
    {
        System.out.println("无参数的test方法");
    }

    /**
     * 有参数的test
     * @param msg
     */
    public void test(String msg)
    {
        System.out.println("重载的test方法" + msg);
    }

    public static void main(String[] args) {
        Overload o = new Overload();
        o.test();
        o.test("Hello");
    }
}

成员变量和局部变量

成员变量的分类图如下:
这里写图片描述
使用示例,代码如下:

public class Person {
    public String name;
    public static int eyeNum;
}

调用:

// 访问类变量
System.out.println(Person.eyeNum);
// 访问实例变量
Person p = new Person();
System.out.println(p.name);
// 为实例变量赋值
p.name = "孙悟空";
// 为类变量赋值
Person.eyeNum = 2;
System.out.println(p.name+ "," + Person.eyeNum);
Person p2 = new Person();
// 类变量不会随着实例变量变化
System.out.println(p2.eyeNum);

只要离开了代码块局部变量所在的代码块,这个局部变量就立即被销毁,变为不可见。

{
    int a =1;
}
// 无法得到a变量
System.out.println(a);

Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。

成员变量的初始化和内存中的运行机制

当系统加载类或创建类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。
代码实例

public class Person {
    public String name;
    public static int eyeNum;
}

调用上面的Person实例

// 创建第一个Person对象
Person p1 = new Person();
// 创建第二个Person对象
Person p2 = new Person();

// 为两个实例变量赋值
p1.name = "孙悟空";
p1.name = "猪八戒";
// 为两个类变量赋值
p1.eyeNum = 2;
p2.eyeNum = 3;

使用演示图说明,如下:
当Person类第一次初始化时,系统会为该类的类变量分配内存空间,并指定默认值。
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

局部变量的初始化和内存中的运行机制

局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化,这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会局部变量分配内存,并将初始化值保存到这块内存中。

与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中的。如果局部变量 是基本类型的变量,则直接把这个变量的值保存在该变量对应内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。

栈内存中的变量无须系统垃圾回收,栈内存中的变量往往是随方法或代码块的运行结束而结束的。因此,局部变量的作用域是从初始化该变量开始,直到该方法或该代码块运行完成而结束。因为局部变量只保存基本类型的值或者对象的引用,因此局部变量所占的内存区通常比较小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值