Javase杂谈(七)

一、构造函数概述与默认构造函数

那么什么是构造函数呢?

既然是函数,那它应该是被定义在类中,同时有函数名,要明确函数返回的结果和参数类型。

我们这里先看一下构造函数的特点:

1.函数名与类名相同;

2.不用定义返回值类型

3.没有具体的返回值。

从这些特点来说,这确实是一个很特殊的函数哦。

我们来看一个定义构造函数的例子:

class Person
{
	private String name;
	private int age;

	//定义一个Person类的构造函数
	Person()//构造函数,而且是空参数的。
	{
		System.out.println("person run");
	}

	public void speak()
	{
		System.out.println(name+":"+age);
	}
}
//测试类
class ConsDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();
	}
}

结果:

那么我们可以这样通俗的定义构造函数:

构造函数:构建创造对象时调用的函数。这样是不是很好理解了。

那么我们就可以清楚的知道构造函数的主要作用:

构造函数的作用:可以给对象进行初始化。

我们必须清楚,创建的每一个对象都必须要通过构造函数初始化。

那么当我们一个类中没有构造函数时,为什么我们还是可以创建对象呢?那是因为有默认构造函数。

什么是默认构造函数呢?

当一个类中如果没有定义过构造函数,那么该类中会有一个默认的空参数构造函数。

class Person(){}//构造函数,而且是空参数的,这其实就是类中的默认构造函数

但我们更要注意的是:如果在类中定义了指定的构造函数,那么类中的默认构造函数就没有了。

比如说我们定义一个类:

class Demo{}//一个什么代码都没有的类

我们说这个类中有没有内容?

答案当然是肯定的,虽然这个类是空的,但是我们可以创建它的对象,也就是说这个类中有一个默认的构造函数:

class Demo
{
	Demo(){}
}

很显然,这个类中确实有内容,就是我们说的默认构造函数。

二、构造函数与一般函数的区别

这一节我们简单学习一下构造函数与一般函数之间的区别所在。

那么它们有什么区别呢,结合上一节,我们可以总结出以下两点区别:

第一个区别:

构造函数:对象创建时,就会调用与之对应的构造函数,对对象进行初始化。

一般函数:对象创建后,需要函数功能时,才调用函数。

这也就是说创建对象时,构造函数一定被调用,而一般函数不一定被调用。

第二个区别:

构造函数:对象创建时,会调用且只调用一次。

一般函数:对象创建后,可以被调用多次。

简单看看示例:

class Person
{
	private String name;
	private int age;

	Person()
	{
		System.out.println("person run");
	}

	public void speak()
	{
		System.out.println(name+":"+age);
	}
}
class ConsDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();//此时调用了构造函数
		p.speak();//调用两次speak方法
		p.speak();
	}
}

我们可以看到当创建一个对象p是,调用了Person类中的构造函数,而且是只调用了一次。而对于一般方法speak方法,是我们在创建了对象之后,才调用,并且我们可以调用任意次娄。

三、构造函数的重载

要使一个Person对象一出生就有名字,那么我们可以定义如下一个构造函数:

class Person
{
	private String name;
	private int age;

	Person()//构造函数,分别为成员变量name和age赋固定的值
	{
		name = "baby";
		age = 1;
		System.out.println("person run");
	}

	Person(String n)//构造函数,有一个初始name参数
	{
		name = n;
	}

	public void speak()
	{
		System.out.println(name+":"+age);
	}
}

我们来测试一下:

class ConsDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();
		p.speak();
		Person p1 = new Person("旺财");
		p1.speak();
	}
}

看一下结果:

同理,我们可以定义另外一个构造函数,把name和age两个参数同时初始化

class Person
{
	private String name;
	private int age;

	Person()//构造函数,分别为成员变量name和age赋固定的值
	{
		name = "baby";
		age = 1;
		System.out.println("person run");
	}

	Person(String n)//构造函数,有一个初始name参数
	{
		name = n;
	}

	Person(String n,int a)
	{
		name = n;
		age = a;
	}

	public void speak()
	{
		System.out.println(name+":"+age);
	}
}

测试:

class ConsDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();
		p.speak();
		Person p1 = new Person("旺财");
		p1.speak();
		Person p2 = new Person("小强",10);
		p2.speak();
	}
}

结果:

从上面我们看到,一个类中我们定义了多个构造函数,它们的参数各不相同,这种现象就是构造函数的重载。

通过构造函数的重载,我们可以通过不同的构造函数初始化不同的对象。

四、构造函数的内存加载

我们之前说过,构造函数只有创建对象时才会被调用并且只调用一次。那么在创建对象的过程中内存中的变化是什么样的呢?

我们接着上一节的Person类,我们分析

class Person
{
	private String name;
	private int age;

	Person()//构造函数,分别为成员变量name和age赋固定的值
	{
		name = "baby";
		age = 1;
		System.out.println("person run");
	}

	Person(String n)//构造函数,有一个初始name参数
	{
		name = n;
	}

	Person(String n,int a)
	{
		name = n;
		age = a;
	}

	public void speak()
	{
		System.out.println(name+":"+age);
	}
}

我们用下面的两行代码来分析一下构造函数在内存中的加载过程

class ConsDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person("小强",10);
		p.speak();
	}
}

对于上面的测试,我们分析它的运行过程:
1.main方法进栈内存,main方法中有一个Person类类型变量p;

2.new创建Person对象,在堆内存中创建空间(假如地址为0x0045),该空间中有两个成员变量name和age;

3.对对象的两个成员变量进行初始化,此时会自动选择调用构造函数Person(String n,int a);

4.构造函数Person(String n,int a)进栈内存,并且有参数n="小强",a=0;

5.然后在堆内存中把参数n和a的数值初始化name和age变量,此时对象的初始化完成;

6.把地址0x0045赋给main方法中的变量p;

7.构造函数Person()出栈,释放参数n和a;

8.执行p.speak()语句,调用Person类中的speak()方法,则speak方法进栈;

9.执行打印语句,跳出speak方法,speak方法出栈;

10.跳出main方法,main方法出栈,程序运行结束。

我们在上面通过对一个简单的对象创建过程进行了分析,简单的学习了构造函数在内存中的加载和运行过程,这里也就是突出了对象的初始化,如果类中没有定义构造函数,那么我们在创建对象时会调用默认的构造函数,而当我们定义了构造函数,则会通过参数类型选择不同的构造函数进行对象的初始化,而且我们知道对象都必须被初始化,初始化就会调用相应的构造函数,所以说,构造函数是必须会进栈内存的。

五、构造函数需要注意的几个细节

那么我们在定义和调用构造函数时,需要注意哪些细节呢?

第一个需要注意的细节:构造函数与set方法

看两个函数代码:

class Person
{
	private String name;
	private int age;
	//构造函数,初始化name
	Person(String n)
	{
		name = n;
		System.out.println(name+"age"+age);
	}
	//一般函数,设置name
	public void setName(String n)
	{
		name = n;
	}
}

我们看到上面有两个函数,第一个是构造函数,第二个是一般方法,它们两个都是为设置name的内容,那么我们能不能用构造函数代替set方法呢,很显然,这是不行了,因为我们之前已经学习了构造函数和一般函数的区别,而set方法就是一个一般函数。构造函数在这里只是对name进行了一次初始化,之后就不再作用了,而set方法当我们需要更改名字的任何时候都可以调用。

第二个需要注意的细节:构造函数可以调用一般函数,但是一般函数不可以直接调用构造函数。

我们来看例子:

class Person
{
	private String name;
	private int age;
	//构造函数,初始化name
	Person(String n)
	{
		setName(n);
		System.out.println(name+":"+age);
	}
	//一般函数,设置name
	public void setName(String n)
	{
		name = n;
	}
}

编译通过,我们来测试一下

class PersonTest 
{
	public static void main(String[] args) 
	{
		Person p = new Person("小强");
	}
}

结果:

很显然构造函数中调用一般函数是可以的。

当我们在set方法中调用构造函数:

class Person
{
	private String name;
	private int age;
	//构造函数,初始化name
	Person(String n)
	{
		name = n;
		System.out.println(name+":"+age);
	}
	//一般函数,设置name
	public void setName(String n)
	{
		Person(n);
		name = n;
	}
}

结果:

我们看到编译直接通不过,所以说一般函数中是不能直接调用构造函数的。

第三个需要注意的细节:与类名同名的一般方法

我们看这样一个函数:

class Person
{
	private String name;
	private int age;

	void Person(String n)
	{
		name = n;
		System.out.println(name+":"+age);
	}
}

测试:

class PersonTest 
{
	public static void main(String[] args) 
	{
		Person p = new Person("小强");
	}
}

结果:

我们看到提示说无法将构造函数应用到给定类型,实际没有参数,但我们的代码中有参数“小强”,也就是说我们创建对象用的函数不是构造函数,而是一个一般函数,程序没有在Person类中找到相应的构造函数,因为我们的代码中没有定义构造函数,那么就只有默认构造函数,而默认构造函数是没有任何参数的。所以我们在以后的编程过程中一定要注意这种现象。

第四个需要注意的细节:构造函数中有return语句。

我们看代码说明问题

class Person
{
	private String name;
	private int age;

	Person(String n,int a)
	{
		if(a<0)
		{
			System.out.println("初始化不合法!");
			return;
		}
		name = n;
		age = a
		System.out.println(name+":"+age);
		return ;//我们在函数那里学习了,没有返回值的函数中的return语句是可以省略的
	}
}

我们来测试一下

class PersonTest 
{
	public static void main(String[] args) 
	{
		Person p = new Person("小强",-1);
	}
}

结果:

我们发现了程序编译通过并且运行正常,return语句跳出构造函数,那么也就是说构造函数中有return语句。

这一节我们学习了四个构造函数中经常需要注意的细节,在以后的编程过程中我们多多注意。

六、this关键字的原理

class Person
{
	private String name;
	private int age;

	Person(String name,int age)
	{
		this.name = name;
		this.age = age;
	}

	public void speak()
	{
		System.out.println(name+":"+age);
	}
}

class ThisTest 
{
	public static void main(String[] args) 
	{
		Person kobe = new Person("KOBE",37);
		kobe.speak();
	}
}

结果:

所以我们可以说,当成员变量与局部变量重名时,我们可以用this来区分。

那么我们就想明白,this到底代表什么呢?java语言说,this代表的是对象。我们还想明白,this代表的是哪个对象?java语言又说,代表的就是当前对象。

专业点的术语是这样定义this的:this就是所在函数所在对象的引用。说简单点就是:this代表本类对象的引用。

我们自己用通俗点的语言来定义:就是哪个对象调用了this所在的哪个函数,this就代表哪个对象,也就是说this就是这个对象的引用。

比如上面的例子中的kobe调用了构造函数Person(String name,int age),那么我们可说this就可以代表kobe这个对象。

那么我们再来对this在内存中的体现过程分析一下,我们继续7.4的过程,只有小的变动。

1.main方法进栈内存,main方法中有一个Person类类型变量kobe;

2.new创建Person对象,在堆内存中创建空间(假如地址为0x0045),该空间中有两个成员变量name和age;

3.对对象的两个成员变量进行初始化,此时会自动选择调用构造函数Person(String n,int a);

4.构造函数Person(String name,int age)进栈内存,参数name="KOBE",age=0也加载入栈。而此时系统会自动为该栈内存中加载一个对象的引用,也就是this,并且把kobe的堆内存地址赋给this;

5.然后在把this.name和this.age的初始化为栈内存中name和age,这样就很清晰了,this.name和this.age我们可以理解为就this所指堆内存中对象的成员变量,此时对象的初始化完成;

6.把地址0x0045赋给main方法中的实例变量kobe;

7.构造函数Person(String name,int age)出栈,释放参数name和age和this引用;

8.执行kobe.speak()语句,调用Person类中的speak()方法,则speak方法进栈,此时系统也会为speak方法加载一个this引用,指向堆内存中的对象地址(0x0045);

9.执行打印语句,跳出speak方法,speak方法出栈,释放this引用;

10.跳出main方法,main方法出栈,程序运行结束。

通过上面的过程分析,我们可以简单的总结出这样一个结论:当在函数中需要用到调用函数的对象时,就用this关键字。

为了更好的理解这个结论,我们把上面的例子可以标准的写成下面这样:

class Person
{
	private String name;
	private int age;

	Person(String name,int age)
	{
		this.name = name;
		this.age = age;
	}

	public void speak()
	{
		System.out.println(this.name+":"+this.age);
	}
}

我们对这个类方法中的所有成员变量都标准的写成了this.成员变量的格式。

那么我们刚开始看的第一个例子为什么没有this,而结果是也是正确的呢?

很显然,当然对于成员变量与局部变量不重名时,this是可以省略的,但请注意,不是没有,而是省略。

七、this关键字的细节与应用

那么构造函数与构造函数之间是怎么访问呢?

为了掌握这个问题,我们必须要明确一个概念,那就是在一个类中的成员如果要被执行,就必须由对象调用.而每个对象都有一个所属的this.

java语言给了关键字this这个功能,那就是用this调用构造函数,而且也是通过参数不同选择调用不同的构造函数.

我们来看一个例子:

class Person
{
	private String name;
	private int age;

	Person()//构造方法1
	{
		System.out.println("person run");
	}
	Person(String name)//构造方法2
	{
		this.name = name;
	}
	Person(String name,int age)//构造方法3
	{
		this.name = name;
		this.age = age;
	}

	public void speak()
	{
		System.out.println(this.name+":"+this.age);
	}
}
class ThisTest 
{
	public static void main(String[] args) 
	{
		Person kobe = new Person("科比",37);
		kobe.speak();
	}
}

对于上面的例子我们可以看到,构造函数3中的第一句语句其实我们已经通过构造函数2实现了,那么为了提高代码的复用性,我们为什么不调用构造函数2呢,java语言中用下面的语句来调用构造函数:

	Person(String name,int age)//构造方法3
	{
		this(name);//用this关键字调用了构造函数Person(String name)
		this.age = age;
	}

我们看到了this关键字可以用于在构造函数中调用其他构造函数,当然对于调用那个构造函数,还是通过参数来确定.

那么我们就明确了构造函数与构造函数之间的调用形式.

下面我们来看两个this关键字用法中需要注意的两个细节:

第一个细节:构造函数中调用构造函数只能定义在构造函数的第一行.

这是为什么呢,因为初始化动作一定要先执行.这就是java语言定义的一个规则,如果不是定义在第一行,编译直接通不过.

我们看例子,把上面的构造函数3的语句交换位置:

	Person(String name,int age)//构造方法3
	{
		this.age = age;
		this(name);//用this关键字调用了构造函数Person(String name)
	}

我们看编译情况:

我们看到编译错误:对this的调用必须是构造器中的第一个语句,这就是我们在使用this关键字时的第一个细节.

第二个细节:注意构造函数的调用出现递归循环而导致栈内在溢出.

我们看个例子:

	Person()//构造方法1
	{
		this("KOBE");
		System.out.println("person run");
	}
	Person(String name)//构造方法2
	{
		this();
		this.name = name;
	}

结果:

我们很容易发现两个构造函数相互调用,形成了递归,使得两个构造函数不断进栈,最后栈内存溢出,程序终止.

上面我们看了两个在使用this关键字时需要注意的细节,下面我们再简单的看看this关键的应用情况.

我们一般什么时候使用this呢,我们上一节中说过this的概念:this就代表对象,就是所在函数所在对象的引用.那么我们不难理解,当我们在一个类中用到了本类的对象,我们就通常会用this来引用.

那么我们来实现一个功能:判断两个人是否是同龄人.

分析一下:要判断两个人是否同龄,我们只需要比较这两个人(也就是两个Person对象)的年龄是否相等即可,那么也就是说一个对象可以直接调用Person类中的方法来与另一个对象做比较就可以了.

我们可以这样实现:

	//判断两个人是否同龄
	public boolean compare(Person p)
	{
		return this.age == p.age;
	}

我们来测试一下:

class ThisTest 
{
	public static void main(String[] args) 
	{
		Person kobe = new Person("科比",37);
		Person james = new Person("詹姆斯",31);
		kobe.speak();
		james.speak();
		System.out.println("这两个人相等吗:"+kobe.compare(james));
	}
}

结果:

我们看到this的主要应用就是代表对象,那个对象调用了this所有的函数,我们就通俗的认为this就代表那个对象.

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

撩得Android一次心动

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值