可用于给对象进行初始化,对象一建立就会调用与之对应的构造函数。
构造函数和一般函数在写法上有不同,在运行上也有不同。构造函数在对象创建时执行,给对象初始化,而且只执行一次。而一般函数是对象调用时才执行,是给对象添加对象具备的功能。一个对象建立,构造函数只运行一次,而一般函数可以被该对象调用多次。你不仅就要问当初new对象时,也没有构造函数啊?那这个对象是怎么初始化的呢?
例,
class Car
{
// Car() {} // 类中默认的空参构造函数,专门用于创建对象初始化用的
}
class CarDemo
{
public static void main(String[] args)
{
Car c = new Car();
}
}
细节一:构造函数中有return语句吗?可以有,用于结束初始化的。如:
Person(String n)
{
name = n;
return;
}
细节二:一般函数的名字是可以和类名一样的,但是不符合书写规范。如以下代码是可以通过编译的。
class Person
{
void Person() // 可以编译通过,一般函数的名字是可以和类名一样的,但是不符合书写规范
{
}
void showPerson() {}
}
关于构造函数,总结如下:
根据如下代码,你能画出构造函数的内存图吗?
class Person
{
private String name;
private int age;
Person(String n)
{
name = n;
}
Person(String n, int a)
{
name = n;
age = a;
}
public void setName(String n)
{
name = n;
}
public String getName()
{
return name;
}
public void setAge(int a)
{
age = a;
}
public int getAge()
{
return age;
}
public void show()
{
System.out.println("name = " + name + ",age = " + age);
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p1 = new Person("xiaoqiang", 30);
p1.show();
}
}
下面我们通过构造函数之间的调用来引出this关键字的讲解。试着思考一下,若构造函数私有,则就表示只在本类中有效,那么该如何访问呢?这里有个注意点,即构造函数只能被构造函数调用,不能直接被一般方法调用。还是回到我们的问题中来,构造函数之间该如何访问呢?答案是通过关键字this来解决。
this看上去,是用于区分局部变量和成员变量同名情况(当成员变量和局部变量同名时,可以通过this关键字区分)的,那么this到底代表的是什么呢?答案是:this代表本类的对象,到底代表哪一个呢?this代表它所在函数所属对象的引用,简单说:哪个对象在调用this所在的函数,this就代表哪个对象。
其实在Java里面有一个机制:只要用对象调用了方法,这个方法里面就持有一个引用,该引用指向哪个对象呢?哪个对象调用我,我就指向谁。总结一点就是:只要是直接被对象调用的方法都持有this引用(或者亦可说凡是访问了对象中的数据的方法都持有this引用)。
根据如下代码,你能画出this关键字在内存中的体现吗?如果你能真能画出,相信你一定对this关键字有了较深的理解。
class Person
{
private String name;
private int age;
private Person(String n)
{
name = n;
}
Person(String n, int a)
{
this(n);
age = a;
}
public void show()
{
System.out.println("name = " + name + ",age = " + age);
}
}
class ThisDemo
{
public static void main(String[] args)
{
Person p = new Person("小明", 21);
p.show();
}
}
this关键字的内存图解:
this带上参数列表的方式就可以访问本类中的其他构造函数。比如:this("lisi")
访问的就是本类中带一个字符串参数的构造函数。还有一点要注意,即用于调用构造函数的this语句必须定义在构造函数的第一行,因为初始化动作要先执行。
当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象。但凡本类功能内部使用到了本类对象都用this表示。例,判断两人是否为同龄人。
class Person {
private String name;
private int age;
Person(int age) {
this.age = age;
}
Person(String name) {
this.name = name;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
public void speak() {
System.out.println("name="+this.name+"...age="+this.age);
this.show();
}
public void show() {
System.out.println(this.name);
}
/*
需求:给人用于比较年龄是否相同的功能,也即是否是同龄人。
*/
public boolean compare(Person per) {
return this.age == per.age;
}
}
class PersonDemo3 {
public static void main(String[] args) {
Person p = new Person(20);
Person p1 = new Person(25);
boolean b = p1.compare(p2);
System.out.println(b);
}
}
static关键字是一个修饰符,用于修饰成员(成员变量、成员函数)。当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用:类名.静态成员
。
被static关键字修饰后的成员具备以下特点:
我们知道其使用注意事项之后,那么static关键字在程序开发中,什么时候使用呢?
下面我们着重看一下成员变量和静态变量的区别:
下面我通过一个例子的讲解来加深对static关键字的理解。例,有如下代码:
class Person
{
private String name;
private int age;
// public static String country = "CN"; // 全局变量
static String country = "CN";
Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void show()
{
System.out.println("name = " + name + ",age = " + age);
}
public static void sleep()
{
System.out.println("呼呼");
// System.out.println("呼呼..." + name); // 无法从静态上下文中引用非静态变量name
}
}
class StaticDemo
{
public static void main(String[] args)
{
Person p = new Person("lisi", 21);
p.show();
/*
如果创建对象调用sleep方法,发现,并没有使用对象中的数据。
该对象的建立是没有意义的。
该方法所属于Person.class,可以通过类名的方式来访问。
注意:用类名直接调用的方法必须通过指定修饰符来修饰,就是关键字static
*/
Person.sleep();
}
}
其格式为:
static {
静态代码块中的执行语句
}
特点是随着类的加载而执行,且只执行一次,并优先于主函数。用于给类进行初始化。其应用场景为:类不需要创建对象,但是需要初始化,这时将部分代码存储到静态代码块中。
运行如下代码,你觉得会输出什么?
class StaticCode {
static {
System.out.println("a");
}
}
class StaticCodeDemo {
static {
System.out.println("b");
}
public static void main(String[] args) {
new StaticCode(); // 将StaticCode.class这个类加载进内存
new StaticCode(); // 注意StaticCode.class已经加载进内存,不会再次加载
System.out.println("over");
}
static {
System.out.println("c");
}
}
输出结果为:
b
c
a
over
注意,若StaticCode s = null;
,StaticCode类并没有被加载进内存。只有用到了类中的内容,才涉及类的加载问题,光建立引用,是不会加载的。
其格式为:
{
构造代码块中的执行语句
}
构造代码块定义的是不同对象共性的初始化内容,其作用是给对象进行初始化,对象一建立就运行,而且优先于构造函数执行。它和构造函数的区别是构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。
下面我用一个面试题来加深对静态代码块和构造代码块的理解。运行如下代码,你觉得会输出什么?
class StaticCode {
int num = 9;
StaticCode() {
System.out.println("b");
}
static {
System.out.println("a");
}
{
System.out.println("c"+this.num);
}
StaticCode(int x) {
System.out.println("d");
}
public static void show() {
System.out.println("show run");
}
}
class StaticCodeDemo {
public static void main(String[] args) {
new StaticCode(4);
}
}
输出结果为:
a
c9
d
什么时候使用静态呢?我们要从两方面下手,因为静态修饰的内容有成员变量和函数。
这里以一个例子来说明静态的应用。每一个应用程序中都有共性的功能,可以将这些功能进行抽取,独立封装,以便复用。我们试图建立一个可以对数组进行操作的工具类,该类中提供了获取最值、排序等功能。
分析:虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作,发现了问题:
这时就考虑,让程序更严谨,是不需要对象的。所以可以将ArrayTool中的方法都定义成static的,直接通过类名调用即可。将方法都静态后,可以方便于使用,但是该类还是可以被其他程序建立对象的,为了更为严谨,强制让该类不能建立对象,可以通过将构造函数私有化完成。
/**
这是一个可以对数组进行操作的工具类,该类中提供了获取最值、排序等功能。
@author 李阿昀
@version V1.1
*/
public class ArrayTool {
// ArrayTool() {}
/**
空参数构造函数
*/
private ArrayTool() {
}
/**
获取一个整型数组中的最大值
@param arr 接受一个int类型的数组
@return 会返回一个该数组中的最大值
*/
public static int getMax(int[] arr) {
int max = 0;
for (int x = 0; x < arr.length; x++) {
if(arr[x] > arr[max])
max = x;
}
return arr[max];
}
/**
获取一个整型数组中的最小值
@param arr 接受一个int类型的数组
@return 会返回一个该数组中的最小值
*/
public static int getMin(int[] arr) {
int min = 0;
for (int x = 0; x < arr.length; x++) {
if(arr[x] < arr[min])
min = x;
}
return arr[min];
}
/**
给int数组进行选择排序
@param arr 接受一个int类型的数组
*/
public static void selectSort(int[] arr) {
for (int x = 0; x < arr.length - 1; x++) {
for (int y = x + 1; y < arr.length; y++) {
if(arr[x] > arr[y]) {
swap(arr, x, y);
}
}
}
}
/**
给int数组进行冒泡排序
@param arr 接受一个int类型的数组
*/
public static void bubbleSort(int[] arr) {
for (int x = 0; x < arr.length - 1; x++) {
for (int y = 0; y < arr.length - x - 1; y++) {
if(arr[y] > arr[y+1]) {
swap(arr, y, y+1);
}
}
}
}
/**
给数组中的元素进行位置的置换
@param arr 接受一个int类型的数组
@param a 要置换的位置
@param b 要置换的位置
*/
private static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
/**
用于打印数组中的元素。打印形式是:[element1, element2, ...]
@param arr 接受一个int类型的数组
*/
public static void printArray(int[] arr) {
System.out.print("[");
for (int x = 0; x < arr.length; x++) {
if(x != arr.length - 1)
System.out.print(arr[x]+", ");
else
System.out.println(arr[x]+"]");
}
}
}
接下来,将ArrayTool.class文件发送给其他人,其他人只要将该文件设置到classpath路径下,就可以使用该工具类。但是,很遗憾,该类中定义了多少个方法,对方不清楚,因为该类并没有使用说明书,所以开始制作程序的说明书,java的说明书通过文档注释来完成。即:
javac -d myhelp -author -version ArrayTool.java。
到了最后一个知识点了,即对象的初始化流程。我还是用一个例子来说明,例有如下代码:
class Person
{
private int age = 8; // 显示初始化
{ // 构造代码块。给所有对象进行初始化的。构造函数只给对应的对象初始化。
System.out.println("constructor code run!!!......." + age);
}
Person()
{
// 隐藏了以下三句代码:
// 1. super()调用父类构造函数
// 2. 显示初始化
// 3. 构造代码块初始化。
System.out.println("person run!!!");
}
Person(int age)
{
this.age = age;
System.out.println("person(age) run!!!");
}
}
/*
创建一个对象的流程:
1,加载指定的字节码文件进内存
2,通过new在堆内存中开辟空间,并分配首地址值
3,对对象中的属性进行默认初始化
4,调用与之对应的构造函数,构造函数压栈
5,构造函数中执行隐式的语句super()访问父类中的构造函数
6,对属性进行显示初始化
7,调用类中的构造代码块
8,执行构造函数中自定义的初始化代码
9,初始化完毕,将地址赋值给指定的引用
*/
class ConsCodeDemo
{
public static void main(String[] args)
{
Person p = new Person();
}
}
你能试着说明Person p = new Person();
该句话都做了哪些事情?——创建一个对象的流程为: