(一)构造函数
构造函数:
构造函数作用:给对应的对象初始化。
构造函数格式:
修饰符 函数名(形参列表){
初始化的语句
}
构造函数注意细节:
1.没有返回值类型。
2.构造函数的函数名必须与类名一致。
3.构造函数并不是由我们手动调用的,而是在创建对应对象的时候由jvm主动调用的(每创建一个对象就会调用一次。)
4.如果一个类没有显式地添加一个构造函数,那么java编译器会为该类添加一个无参的构造函数。
5.如果一个类已经显式地添加一个构造函数,那么java编译器不会为该类添加一个无参的构造函数。
6.构造函数可以以函数重载的方式在一个类中添加多个,调用时根据参数列表进行某个构造函数调用。
若自己没有写构造函数的话,构造函数也存在,但不是java虚拟机添加的,而是java编译器添加的。
如何证明?反编译
如何反编译:可以使用jdk给我们提供的开发工具--javap
javap -c(反编译代码) -l(返回每一行的行号) -private(显示所有类即所有成员)
javap -c -l -private class文件名
java编译器添加的无参构造函数的权限修饰符是与类名的权限修饰符一致的。
例如:
class Baby
{
int id;
String name;
//带参的构造函数
public Baby(int num,String s){
id=num;
name=s;
System.out.println("有两个参数的构造函数!");
}
//无参的构造函数
public Baby(){
System.out.println("无参的构造函数!");
}
public void cry(){
System.out.println("哇哇哇~~~");
}
}
class demo1
{
public static void main(String[] args)
{
new Baby();
new Baby();//调用两次无参的构造函数,一次有参的构造函数
Baby b=new Baby(2020,"小勺子");//利用构造函数给变量初始化
b.cry();
System.out.println("身份证号:"+b.id+"姓名:"+b.name);
}
}
(二)构造代码块
构造代码块:给所有的对象进行统一的初始化。
构造代码块格式:
{
初始化语句;
}
每创建一个任意对象,就会执行一次构造代码块(与参数无关),若一个类中有多个构造函数,且都要实现某一功能,那重复的代码就可以写到构造代码块中,减少代码量。
代码块类型:
构造代码块:代码块位于成员位置上,即方法之外,类之内
局部代码块:代码块位于方法之内,可以缩短局部变量的生命周期,很少使用
静态代码块:
要注意的细节:
1.构造函数的代码是位于成员变量的显式初始化语句和构造代码块语句之后执行的,是最后执行的。
2.成员变量的显式初始化与构造代码块的语句的先后执行顺序是按照当前代码的顺序执行的。
3.成员变量的初始化语句和构造代码块的代码其实是在构造函数中执行的,按照代码顺序。
例如:
class Person
{
//成员位置不能编写执行语句(如输出语句,赋值),只能用于声明变量或函数。(语法规定,编译会报错)
{
i=200000;//并不会报错,编译器在编译时会移动顺序
}
int i=1000000;//显式初始化,也是执行语句,编译不会报错是因为,在编译时java编译器已经把赋值的动作移到了构造函数里面。
//构造函数
/*public Person(){
i=3000000;
}*/
}
class demo3
{
public static void main(String[] args)
{
Person p=new Person();
System.out.println(p.i);//没有构造函数时输出100000,有时输出300000
}
}
(三)this关键字
问题引入:
class Animal
{
String name="dog";//成员变量
public void eat(){
String name="cat";//局部变量,说明可以出现同名的成员变量与局部变量
System.out.println(name+"在吃饭");
}
}
class demo4
{
public static void main(String[] args)
{
Animal ani=new Animal();
ani.eat();//输出 cat在吃饭
//System.out.println("Hello World!");
}
}
为什么输出的name是cat,而不是dog呢?
首先,因为java中的一个方法(函数)在运行的时候,jvm会在栈内存中为该方法分配对应的内存空间。所以所有的方法执行都是在栈内存中执行的,所以栈内存也叫方法栈。
上面的代码执行过程:
1.执行main方法,jvm在栈内存中为main方法分配内存空间,
2.执行Animal ani=new Animal()语句 ,Animal ani表示在开辟的内存空间中声明一个Animal类型的变量ani,new Animal()表示在堆内存中创建一个Animal类的对象,并在该对象内存空间中声明一个成员变量name="dog",=表示将堆内存中对象的地址赋给栈内存中的ani。
3.执行ani.eat()语句,调用eat方法,jvm在栈内存中为eat方法分配内存空间,在该空间中声明一个局部变量name="cat",然后是语句System.out.println(name+"在吃饭"),这是name 会找值,name="cat"同在栈内存中,但name="dog"在堆内存中,jvm按照就近原则给输出语句中的name赋值为cat。
所以存在同名的成员变量与局部变量时,在方法内部默认访问的是局部变量。
那么如何访问成员变量,使name值为dog呢?
可以采用this关键字。
this代表的是所属函数的调用者对象。
this作用:
1.一个类存在同名的成员变量与局部变量时,在方法内部默认访问的是局部变量,可以通过this关键字访问成员变量的数据。
2.this关键字可以在构造函数中调用本类中其他的构造函数初始化对象。
注意细节:
1.如果在一个函数中访问一个成员变量,而且没有同名的局部变量时,那么java编译器就会在默认的变量前面加this关键字。
2.this关键字调用其他的构造函数时,this语句必须位于构造函数中的第一个语句。
3.this关键字调用构造函数的时候,不准出现相互调用的情况,因为是一个死循环的调用方式。
例如:
class Animal
{
int id;//编号
String name;//成员变量
String color;
//构造函数
/*public Animal(String name,String color){//形参也属于局部变量
name=name; //左右两边都是局部变量,并没有给成员变量赋值,所以是null
color=color;
}*/
public Animal(String name,String color){//形参也属于局部变量
this.name=name; //这样才会给成员变量赋值
this.color=color;
}
public Animal(int id,String name,String color){
this(name,color);//调用两个参数的构造函数,节省下面两行代码重复写
this.id=id;
//this.name=name;
//this.color=color;
//this();//表示调用本类中无参的构造函数,不存在,编译报错
}
public void eat(){
String name="cat";//局部变量,说明可以出现同名的成员变量与局部变量
System.out.println(this.name+"在吃饭");
}
public void sleep(){
System.out.println(name+"在睡觉");
}
}
class demo4
{
public static void main(String[] args)
{
Animal ani1=new Animal("哈士奇","白色");
Animal ani2=new Animal(1111,"泰迪","棕色");
//ani1.sleep();//输出
System.out.println("姓名:"+ani1.name+"颜色:"+ani1.color);
System.out.println("编号:"+ani2.id+"姓名:"+ani2.name+"颜色:"+ani2.color);
}
}
(四)static关键字
static(静态):修饰符
static修饰成员变量:若一个数据需要被所有对象共享使用的时候,那么即可使用static修饰该成员变量。
访问方式:
方式1:可以使用对象进行访问。格式:对象.静态属性名
方式2:可以使用类名进行访问。格式:类名.属性名
推荐使用:方式2,从内存的角度思考,用对象访问需要先创建一个对象,要耗费内存,但是类名访问不用内存。
静态成员变量注意细节:
1.静态成员变量可以使用对象、类名访问。
2.非静态成员变量只能用对象进行访问。
3.千万不要为了方便访问就用static修饰成员变量,只有这个变量需要被共享的时候,采用static修饰。
例如:
class Student
{
String name;//非静态成员变量
static String country="中国";//静态成员变量使用static修饰,那么该数据会进入方法区(数据共享区)内存中,且只有一份
//构造函数
public Student(String name){
this.name=name;
}
public void study(){
System.out.println(name+"好好学习");
}
}
class demo5
{
public static void main(String[] args)
{
Student s1=new Student("小红");
Student s2=new Student("小花");
//System.out.println("你好世界!!!");
System.out.println("姓名:"+s1.name+" 国家:"+s1.country);
System.out.println("姓名:"+s2.name+" 国家:"+s2.country);
}
}
static修饰成员函数:
方式1:用对象访问。格式:对象.函数名()
方式2:用类名访问。格式:类名.函数名()
静态函数要注意的细节:
1.非静态函数不能用类名调用,只能用对象调用。
2.静态函数可以直接访问静态的成员,但是不可以直接访问非静态的成员。
原因:静态函数可以用类名调用,这时候可能对象还没有在内存中,也就没有非静态成员,只能访问静态。
3.非静态函数可以直接访问静态和非静态的成员。
原因:非静态函数必须要由对象调用。因为静态成员先于对象存在,非静态随着对象的存在而存在,有非静态函数说明已有对象,则也已有静态和非静态成员,所以都可以访问。
4.静态函数不能出现this关键字和super函数。
原因:因为静态函数可以使用类名直接调用,而这时可能还没有对象存在,但this又需要代表对象,两相矛盾。
关键:静态数据是优先于对象而存在的。
静态成员变量与非静态的成员变量的区别:
数量上的区别:
(n份)非静态成员变量是在每个对象中都维护一份数据。
(1份)静态成员变量只在方法区中维护一份数据。
访问方式上的区别:
非静态的只能用对象访问。
静态的可以用对象、类名访问。
存储位置上的区别:
非静态的在堆内存中。
静态的在方法区中。
生命周期的区别:
非静态的随对象的创建而存在,随对象消失而消失。
静态的是随着类文件加载而存在,随着类文件(字节码文件)的消失而消失。
(字节码文件一旦被加载到内存中就一直存在,很难消失,除非jvm退出。能够被使用多次)
(一个类文件被加载到内存(方法区)中的时候,jvm会对该类文件进行解剖,分析出该类具备哪些成员以及哪些静态数据。)
作用上的区别:
非静态的作用是用于描述一类事物的属性的。
静态的作用是提供一个共享数据给所有的对象使用的。
何时用static修饰一个函数呢?
如果一个函数没有直接访问非静态的成员,就可以用static修饰。(常用于工具类方法,如Arrays)