目录
一、构造函数
对于人这类事物而言,一般每个人出生之后,就会有名字和年龄。那么也就是说这个事物一旦被创建出来,就应该具备一些属于自己的数据。
当我们在创建一个对象的时候,如果需要对象创建好之后,就必须拥有一些属于自己本身的特定数据,而不是通过程序去修改。那么也就是说在创建对象的过程中,就应该把这些数据赋值完成。
//演示构造函数
class Student
{
String name;
int age;
String address;
void speak()
{
System.out.println("name="+name+",age="+age+",address="+address);
}
}
class ConstructorDemo
{
public static void main(String[] args)
{
Student s = new Student();
s.speak();
}
}
在ConstructorDemo 类中,创建完的Student对象所有的属性都是默认的值, 而在现实生活中,每个学生入校之后,都会有自己的年龄、姓名、地址。那么我们更希望在对象创建完之后就让每个学生具备属于自己的这些数据,而不是通过对象创建完之后,再通过其他的代码进行修改。
要解决这个问题,就必须使用Java中提供的构造函数完成。
1.1、构造函数的介绍
构造函数(函数):构造,就是构建和创造。在Java中的构造函数(函数)它的意思是用来完成对象的创建的。
在我们的程序中如果使用new 关键字,这时就是在创建对象,而我们在创建对象的过程中,其实JVM就在调用当前这个构造函数。
既然在new对象的时候,会调用对应的函数,这时我们就可以在new对象的过程中,传递参数,完成学生信息的初始化动作。
一般函数的定义格式:
修饰符 返回值类型 函数名(参数列表)
{
}
构造函数(函数)格式:
修饰符 构造函数名( 参数列表 )
{
}
构造函数定义的细节:
- 构造函数是用来创建对象的,它不需要书写返回值类型。
- 构造函数的名字要求必须和当前所述的类名一致。因此构造函数的名称书写和类名一致。
- 参数列表,可以和一般函数的参数列表一样。
//演示构造函数
class Student
{
String name;
int age;
String address;
//添加构造函数,在创建对象的时候,完成对姓名 年龄 地址的初始化
Student( String nm , int a , String addr )
{
name = nm;
age = a;
address = addr;
}
void speak()
{
System.out.println("name="+name+",age="+age+",address="+address);
}
}
class ConstructorDemo
{
public static void main(String[] args)
{
Student s = new Student( "班长" , 18 , "郑州");
s.speak();
}
}
当我们在程序描述一个事物的时候,就是在写一个类,这时如果这类事物产生的对象,在对象创建完成之后,就必须明确属于这个对象的一些初始化数据,这时在描述这个事物的那个类中,一定要书写对应的构造函数。
构造函数的功能:
在创建对象的过程中,根据new对象时传递的数据,对当前创建出来的这个对象中的成员变量进行指定数据的初始化。
1.2、构造函数执行和内存图解
在创建对象的过程中构造函数的执行时机:
- 通过new 关键字创建对象,首先会在堆中分配对象的内存空间
- 给对象属于的类中的所有成员变量进行默认的初始化动作。
- 开始调用和new对象时传递的参数一致的构造函数。
- 调用了构造函数之后,开始执行构造函数中的所有代码。
- 当把构造函数中的所有代码全部执行完成,这时构造函数出栈,才表示当前这个对象创建完成。
- 把对象的内存地址赋值给对应的引用变量。
1.3、默认构造函数
当我们书写一个类的时候,在这个类中如果不写任何的构造函数,那么在使用javac编译完这个java源代码之后,生成的class文件中,会自动的添加一个没有参数的构造函数。
把这个构造函数称为默认的构造函数。
上述的Teacher类编译完之后,class文件中就会添加一个 Teacher(){} 构造函数。在new对象的时候,没有传递任何数据,就会调用这个构造函数。
注意:如果我们在写一个类的时候,手动的书写了有参数的构造函数,那么在编译Java源代码的时候,编译器就不会在class文件中添加这个默认的构造函数了。
因此在定义一个类的时候,如果这个类的对象一创建,就需要必须明确自己的属性数据,这时一定不要对外提供空参数的构造函数。
定义类的时候,在类中需要对对象进行初始化,我们一般都会提供有参数的构造函数,同时根据需求也可以提供没有参数的构造函数。
这样就会导致在一个类拥有多个构造函数。那么这些构造函数将以重载的形式存在于当前这个类中。
1.4、构造函数和一般函数异同
1、它们的执行时间不同:
构造函数是在创建对象的过程中执行。当对象创建完成了,构造函数就已经执行结束。
一般函数通过是在对象创建完成之后,通过对象的引用来调用。
2、它们的调用次数不同:
构造函数只有在new对象的时候,会被调用,一旦对象创建完成,我们不能手动的人为去调用构造函数。
一般函数可以通过对象随意的调用,没有次数限制。
3、它们互相调用问题:
构造函数中可以去调用一般的函数,但是在一般的函数中不能调用构造函数。
问题:
如果在一个类中书写了一个函数,函数名和类名一致,但这个函数书写了返回值,这个函数能不能在类中存在?
可以书写,但是开发时不要这样书写。
它会被当做一般函数。
一般方法的调用是需要通过函数名去调用,如果是构造函数之间的调用,这时不能通过构造函数的名字去调用。而需要使用Java中的this关键字完成。
如果要进行构造函数的之间的调用,这时必须使用this关键字完成。
格式:
this(实际参数);
This调用构造函数,必须放在构造函数中的第一句。
在使用this调用构造函数的时候注意的问题:
- 构造函数之间不能相互嵌套调用,这样就会导致无限制的调用构造函数,导致永远无法停止调用。
- this调用构造函数,必须放在构造函数中的第一句。我们通过this调用构造函数的目的是希望通过其他的构造函数完成初始化动作, 因此要求其他构造函数的初始化必须在本构造函数中语句执行之前先初始化完成。
二、this关键字
2.1、this调用构造函数
在一个类中可以书写多个构造函数,不同的构造函数执行是根据在new对象的时候根据具体传递的实参决定最后调用哪个构造函数。
而所有的构造函数都有相同的功能,就是给当前new的这个对象进行成员变量的初始化。
class Student
{
String name;
int age;
//这个构造函数可以对象成员变量name和age进行赋值
Student(String nm , int a)
{
name = nm;
age = a;
}
//这个构造函数可以对象成员变量name进行赋值
Student(String nm )
{
name = nm;
}
}
在上述的Student类中,有2个构造函数,但是其中都有相同的功能,可以完成name的初始化动作,这时我们没有必要再所有的构造函数中都书写相同功能的代码,而只需要在某一个构造函数中写完,在其他的构造函数中调用这个已经存在的功能。
如果要进行构造函数的之间的调用,这时必须使用this关键字完成。
格式:
this(实际参数);
在Java中只要是通过名称调用函数,那么调用的都是一般函数,构造函数之间不能通过函数名调用。
class Student
{
String name;
int age;
//这个构造函数可以对象成员变量name和age进行赋值
Student(String nm , int a)
{
//Student(nm);
this(nm);
//name = nm;
age = a;
}
//这个构造函数可以对象成员变量name进行赋值
Student(String nm )
{
System.out.println("aaaaaaaaaaa");
name = nm;
}
}
在使用this调用构造函数的时候注意的问题:
- 构造函数之间不能相互嵌套调用,这样就会导致无限制的调用构造函数,导致永远无法停止调用。
- this调用构造函数,必须放在构造函数中的第一句。我们通过this调用构造函数的目的是希望通过其他的构造函数完成初始化动作, 因此要求其他构造函数的初始化必须在本构造函数中语句执行之前先初始化完成。
2.2、this的原理图解
在构造函数被调用的过程中,肯定是堆中某个对象对自己的成员变量进行默认初始化之后调用了与之对应的构造函数。在当前被调用的这个构造函数中会有一个隐式的this变量,它记录着当前自己这个构造函数是被具体哪一个对象所调用。
记住:不管是构造函数,还是一般函数,只要是通过对象调用的,那么在这些函数中都有一个this变量存在,记录着当前调用这个函数的那个对象。
this:这个。谁调用了this所在的函数,this就表示谁。
1.3、成员变量和局部变量同名问题
当在函数中局部变量和成员变量同名的时候,在函数中如果要访问成员变量,这时需要使用this来区分:
格式:
this.成员变量名;
this表示的是当前的那个对象,this.成员变量,当前这个对象的成员变量。
如果在函数中没有和成员变量同名的局部变量名时,在函数中可以省略this.
总结:
this的应用:
- this是用来记录当前调用这个函数的那个对象。
- this可以在构造函数中完成调用其他的构造函数。格式 this( 实际参数 );
- this可以区分成员变量和局部变量。格式:this.成员变量名 ;
class Student
{
String name;
int age;
//这个构造函数可以对象成员变量name和age进行赋值
//开发中定义函数的参数,或者定义的普通变量,变量名一定要见名知意
Student(String name , int age )
{
this.name = name;
this.age = age;
}
void say()
{
System.out.println("this="+this + ",name="+this.name+",age="+this.age);
}
}
class ThisDemo3
{
public static void main(String[] args)
{
Student s = new Student("王文翔",23);
System.out.println("s="+s);
s.say();
}
}
1.4、this的练习
/*
需求:描述人这类事物,其中必须提供功能:判断是否是同龄人。
分析需求的时候,只针对当前这个需求进行属性和行为的分析。不要超越问题的领域。
分析:
1、描述事物就必须使用类体现
2、人这个事物最少需要具备年龄属性
3、还需要一个比较自己和别人是否年龄相同的功能。
*/
class Person
{
private int age;
//以创建Person对象就具备自己的年龄
Person( int age )
{
this.age = age;
}
void setAge( int age )
{
this.age = age;
}
int getAge()
{
return this.age;
}
//提供比较是否是同龄人的功能
/*
1、需要返回值吗?需要,什么类型?boolean
2、需要参数吗?几个? 需要 1个,被比较的那个Person对象
*/
boolean compareByAge( Person p )
{
/*
当这个函数运行的时候需要一个Person对象来调用
在函数运行的过程中其实已经有一个隐式的Person对象存在,就是调用当前这个函数的那个Person对象
而这个函数的功能是笔记自己和别人是否是同龄人,
自己就是调用这个函数的那个Person对象,而我们只需要知道比较的另外一个人的信息即可。
if( this.age == p.age )
{
return true;
}
else
{
return false;
}
*/
return this.age == p.age ? true : false;
}
}
class ThisTest
{
public static void main(String[] args)
{
Person p = new Person(28);
Person p2 = new Person(18);
//p就是表示自己, p2 表示对方
//调用自己的compareByAge 函数, 把对象传递进去
boolean boo = p.compareByAge( p2 ) ;
System.out.println("boo="+boo);
}
}
2、static(静态)关键字
2.1、static静态的引入
class Person
{
String name;
int age;
void sleep()
{
System.out.println("Zzzzz...............");
}
void speak()
{
System.out.println("name = "+this.name + ",age = "+this.age);
}
}
class StaticDemo
{
public static void main(String[] args)
{
Person p = new Person();
p.sleep();
Person p2 = new Person();
p2.sleep();
}
}
在StaticDemo类中的main方法中,我们创建了2个Person对象,仅仅只是为了调用sleep方法。而在sleep方法中并没有访问到和当前对象有关的任何成员变量数据。
我们创建对象的时候,对象会在堆中出现,并且所有的成员变量都会随着当前这个对象的创建在堆存在,并且这些成员变量就来描述当前这个对象的所有属性数据。
我们能不能在不创建对象的前提下就可以调用sleep函数呢?
由于我们目前所学的技术,要调用某个类中的函数, 必须先创建这个类的对象,然后通过对象调用函数。
如果在程序中,仅仅只是为了调用函数,而函数中并没有访问到这个对象自己的一些数据,那么这时可以不使用对象,同时就不用再程序创建这个对象。
这时我们可以使用Java中提供的static关键字修饰这个函数,这样就可以直接通过当前函数所在的类名直接调用函数, 而不需要对象。
2.2、static修饰函数
当一个函数中没有访问到这个类创建出的对象中的数据时,这个函数才能被static修饰:
static修饰函数的格式:
修饰符 返回值类型 函数名( 参数 )
{
函数体;
}
修饰符:就是可以是static
被static关键字修饰的函数,它不需要对象,可以直接通过 类名.函数名(实际参数) 方式调用
2.3、静态函数使用事项
- 静态关键字是一个修饰符。可以修饰类中的成员函数和成员变量。不能修饰构造函数。
- 静态函数它是在类加载的时候,就在内存中加载完成,可以直接运行的函数。
非静态函数,它的运行必须是在类加载完成之后,通过new关键字创建出对象之后,通过对象才能调用。
3、静态函数中不能调用非静态函数。
因为静态函数在类加载完成之后通过类名可以直接调用,而这时很有可能还没有创建对象。非静态函数必须依赖于对象才能运行。
4、非静态函数中是可以调用静态函数的。
当非静态函数可以运行的时候,在内存中一定有个对象,既然有对象了,就说明对象所属的那个类肯定已经被加载完成了。类都加载完成了,静态函数已经准备就绪。
5、静态函数中不能使用this 和 super关键字。
this关键字它表示的是当前调用这个函数的那个对象。而在静态函数中是没有对象的。
2.4、main方法静态解释
主方法的格式:
public static void main(String[] args)
{
}
1、main方法是JVM调用的,它是程序的入口。
JVM是如何调用主方法的呢?
一个class文件要能够运行,首先需要启动JVM,然后JVM会去这个class文件中找main方法
在dos窗口输入 java Demo 回车,启动JVM,分配内存空间,然后从硬盘上加载Demo.class文件。
加载完成之后,JVM会直接通过当前这个类名调用当前类中的main方法。
Demo.main();
- 因为JVM是直接通过类名调用的main方法, 这样要求main方法必须是静态的。
- void 它表示main方法不需要给JVM返回任何的数据。
- main 它是函数名,虽然不是关键字,但是程序的入口,JVM只认main这个函数名。
- String[] args 它是程序运行的时候,接受JVM传递给main方法的参数。接受JVM传递给main方法的字符串数据。
2.5、静态修饰成员变量
静态关键字是一个修饰符,它主要用来修饰类中的成员(变量和函数)。
//描述一个圆
class Circle
{
//圆必须有半径和圆周率
private double radius ;
private double PI = 3.14;
//构造函数
Circle( double radius )
{
this.radius = radius;
}
//对外提供一个计算圆面积功能
double getArea()
{
return radius * radius * PI;
}
}
class StaticDemo2
{
public static void main(String[] args)
{
//使用static关键字修饰类中的成员变量
Circle c = new Circle(4);
double area1 = c.getArea();
System.out.println("area1="+area1);
Circle c2 = new Circle(6);
double area2 = c2.getArea();
System.out.println("area2="+area2);
}
}
上述程序有问题:
创建的所有圆对象中都一个相同的属性数据,而把这个数据随着对象的创建在堆中保存,对象越多,浪费的内存空间就会越多。
如果一个成员变量的值是所有对象都相同,这时我们可以让这个变量在内存只有一个,然后所有对象共享这个值。
要解决这个问题,就可以通过静态关键字来修饰这个成员变量,当某个成员变量被静态关键字修饰了,这个成员变量就变成所有对象共享的一个变量,并且这个变量所在的内存空间也发生了改变。
//描述一个圆
class Circle
{
//圆必须有半径和圆周率
private double radius ;
private static double PI = 3.14;
//构造函数
Circle( double radius )
{
this.radius = radius;
}
//对外提供一个计算圆面积功能
double getArea()
{
return radius * radius * PI;
}
}
2.6、静态成员变量和非静态成员变量的区别
1、它们在内存中出现的时间不同:
静态成员变量:它是在加载当前这个类的时候,就在方法区的静态区中存在。
非静态成员变量:当创建这个类的对象的时候,随着对象的产生在堆中出现。
2、它们所在的内存区域不同:
静态成员变量:在方法区的静态区中。
非静态成员变量:堆内存中。
3、它们的初始化时间不同:
静态成员变量:在类加载的时候就会初始化,类加载完成,变量已经初始化结束。
非静态成员变量:它是在对象的创建过程中被初始化。
4、它们的生命周期不同:
静态成员变量:它随着类的加载就在方法区的静态区中一直存在。直到类被从方法区卸载,才会消失。
非静态成员变量:它是随着对象的产生而存在,随着对象的消失就消失。
2.7、静态内存加载
JVM在从硬盘上加载一个class文件的时候,这个class文件中的静态成员需要加载到方法区的静态区中。而所有的非静态成员和构造函数,都需要加载到方法区的非静态区中。
//静态的内存加载
class Demo
{
int x ;
static int y = 3;
static void print()
{
System.out.println("y="+y);
}
void show()
{
System.out.println("x="+x + ",y=" + y);
}
}
class StaticDemo3
{
public static void main(String[] args)
{
Demo.print();
Demo d = new Demo();
d.x = 10;
d.show();
}
}
2、final关键字
final关键字:它可以修饰类,可以修饰成员(成员变量、成员函数),修饰局部变量。
private 和static只能修饰类中的成员(成员变量、成员函数)。
被final关键字修饰的内容都是最终内容,不能被修改。
被final修饰的类,是最终类,它不能再有子类。这个类不能被继承。
被final修饰的函数,这时这个类没有被final修饰,那么这个类是可以有子类,但是它中被final修饰的方法子类不能复写。
被final修饰的变量,它空间中存放的数据永远都不能被修改。因此在开发中,如果一个变量被final修饰了,这时这个变量我们成为常量。
private static final double PI= 3.14;
由于final修饰的变量是常量,我们开发中为了和变量名有区别,因此所有的被final修饰的变量名统一大写。
面试题:
final修饰的变量空间中的值不能改变,final修饰的引用变量空间中的值能否改变?引用变量所指对象中的数据能否改变?
final修饰的引用变量空间中的值不能改变:
final Person p = new Person(); p中的地址永远都不能修改。
引用变量所指对象中的数据是可以改变的。
3、代码块介绍
代码块:使用大括号把代码封装起来,被封装的这部分代码 就称为一个代码块。
3.1、静态代码块
格式:
static
{
写代码。
}
这个代码块在类加载的时候就会执行。一般我们主要使用静态代码块完成程序启动的时候数据的初始化动作。
class StaticCode
{
static int x = 10;
static
{
System.out.println("静态代码块运行....x = " + x);
}
void print()
{
System.out.println("print run.....");
}
}
class StaticCodeDemo
{
public static void main(String[] args)
{
new StaticCode().print();
}
}
在类加载的时候,JVM会先加载类中的所有静态成员变量,当把所有的静态成员变量加载完成之后,开始给类中的所有静态成员变量进行默认初始化,当类中的所有静态成员变量默认初始化完之后,会接着开始给所有静态成员变量进行显示的赋值操作。
只有类中所有的静态成员变量显示赋值结束之后,静态代码块才会运行。
class StaticCode
{
static int y = show();
static int x = 10;
static
{
System.out.println("静态代码块运行....x = " + x);
}
static int show()
{
System.out.println("...x = " + x);
return 100;
}
void print()
{
System.out.println("print run.....");
}
}
class StaticCodeDemo
{
public static void main(String[] args)
{
new StaticCode().print();
}
}
3.2、构造代码块
格式:
{
写代码
}
这个代码块位于类的成员位置上。
class ConstructorCode
{
static
{
System.out.println("静态代码块....................");
}
//构造代码块
{
System.out.println("构造代码块执行。。。。。。");
}
static void show()
{
System.out.println("show run....");
}
}
class ConstructorCodeDemo
{
public static void main(String[] args)
{
//ConstructorCode.show();
new ConstructorCode();
new ConstructorCode();
new ConstructorCode();
new ConstructorCode();
}
}
当在一个类中所有的构造函数中都有部分相同的初始化代码时,这时我们可以对这些相同的代码进行抽取,然后保存在构造代码块中。
它的执行时间:
其实在new对象的时候,会在堆中给对象分配内存空间,并给对象的成员变量进行默认初始化,接着调用对应的构造函数,在构造函数中有隐式的三步,只有把这个三步执行完,JVM才会去执行当前构造方法中的代码。
//隐式的三步 super() ; 成员变量显示(赋值)初始化 ;构造代码块执行
S沙 F发 G狗
3.3、局部代码块
把代码块直接写在其他方法,或者静态代码块,或者构造代码块中的代码。
局部代码块的存在,仅仅是为了限制变量的作用范围,限定变量的存活时间。
3.4、对象的创建过程
类的加载过程:
- 启动JVM,加载程序中需要使用的class文件。
- 在加载class文件的时候,所有的静态内容(静态成员变量,静态成员函数,静态代码块)都要加载到方法区的静态区中。
- 当类中的所有静态加载完成之后,开始给类中的所有静态成员变量默认初始化。
- 类中的所有静态成员变量默认初始化完成之后,开始给这些静态成员变量显示赋值。
- 所有静态成员变量显示赋值结束之后,开始运行类中的静态代码块
- 当所有的静态代码块执行完成,代表当前这个class文件才彻底被加载结束。
对象的创建过程:
- 使用new关键字创建对象,在堆给对象分配内存空间
- 给对象所属类中的所有非静态成员变量分配空间并进行默认的初始化
- 执行和new对象时传递参数一致的构造函数。
- 执行构造函数的的过程中有隐式的三步:
4.1、执行super() 语句,找父类的空参数构造函数
4.2、给成员变量进行显示赋值。
4.3、构造代码块运行
4.4、构造函数中的自己写的代码执行。
5、构造函数执行完成,对象创建结束。
3.5、定义class的时候成员位置上能够写什么?
在类的成员位置上可以书写的内容:
- 成员变量(静态和非静态之分)
- 成员函数(静态和非静态之分)
- 构造函数
- 静态代码块
- 构造代码块
4、单例设计模式
4.1、设计模式介绍
设计模式:它表示的是一套解决问题的方案。针对生活中的常见问题,以及它的解决办法进行总结,最后形成了一套方案。
设计模式起源建筑行业。后期被移植到计算机领域。
在计算机领域中有23种设计模式。
4.2、单例设计模式介绍
单例设计模式:单态、原子设计模式。
单例设计模式:它主要用来保证一个类在程序从启动到最后结束,要保证这个类的对象只有一个。单例就是保证一个类的对象是唯一的。
class 老毕
{
老毕(){}
}
上述的这个类中有一个默认的构造函数,那么在这个类之外的地方就可以随便的创建这个类的对象。就无法保证外界永远只有这个类的一个对象。要解决这个问题,我们可以不让外界new本类对象。
new对象是要调用这个类的构造函数,如果我们把这个类的所有构造函数全部给私有起来,这样外界就无法访问这个类中的构造函数了,进而就防止外界随意创建对象。
把类中构造函数私有,外界是无法访问到了,但是在本类中,自己是可以访问的。我们就可以在这个类中创建本类的对象,然后给外提供相应的访问功能,让外界获取本类的对象。
class 老毕
{
//私有构造函数
private 老毕(){}
//创建本类对象
private static 老毕 lb = new 老毕();
//对外提供获取本类对象的函数
public static 老毕 get老毕()
{
return lb;
}
void speak()
{
System.out.println("老毕在讲Java");
}
}
class SingleClassDemo
{
public static void main(String[] args)
{
老毕 lb = 老毕.get老毕();
lb.speak();
}
}
要保证一个类的对象唯一,可以使用单例设计模式:
书写类的方式和以前一样,我们只需要在原有的类上加上如下三步:
- 私有本类中的所有构造函数。
- 在本类中创建本类对象。
- 对外提供获取本类对象的函数。
4.3、单例设计代码体现
//饿汉式
class Single
{
//私有构造函数
private Single(){}
//创建本类对象
private static Single instance = new Single();
//对外提供获取本类对象的函数
public static Single getInstance()
{
return instance;
}
}
懒汉式 :
(饱汉式:)
class Single2
{
//私有构造函数
private Single2(){}
//创建本类的对象的引用
private static Single2 instance = null;
//对外提供获取本类对象的函数
public static Single2 getInstance()
{
if( instance == null )
{
instance = new Single2();
}
return instance;
}
}
class SingleClassDemo2
{
public static void main(String[] args)
{
Single2 s = Single2.getInstance();
Single2 s2 = Single2.getInstance();
System.out.println("s="+s);
System.out.println("s2="+s2);
}
}
4.4、单例设计举例
超人:世界上只有一个超人。