类的成员之一:成员变量

成员变量的分类

* 实例变量:没有static修饰,也叫对象属性,属于某个对象的,通过对象来使用
* 类变量:有static修饰,也叫类变量,属于整个类的,不是属于某个实例、
如何声明成员变量

成员变量的类型可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等 例如:声明一个中国人的类
class Chinese{ static String country;//类变量 String name;//默认值 char gender =
'男';//显式赋值 }
如何在类外面访问成员变量

类变量

成员变量/实例变量 

代码示例
class Chinese { static String country; String name; char gender = '男'; }
public class Demo { public static void main(String[] args) {
//System.out.println(Chinese.name);错误,非静态成员变量必须通过对象.进行访问 //创建对象 Chinese c1 =
new Chinese(); //对象名.非静态成员变量 System.out.println(c1.name);//null //对象名.非静态成员变量
System.out.println(c1.gender);//男 //类名.静态成员变量,推荐
System.out.println(Chinese.country);//null //静态的成员变量也可以通过对象.进行访问,不推荐
System.out.println(c1.country);//null } }
成员变量有默认值

 类变量的值是所有对象共享的,当类变量发生改变的时候,任何此类的对象访问该变量都会发生改变。而实例变量的值是每个对象独立的。
class Chinese { static String country; String name; char gender = '男'; }
public class Demo { public static void main(String[] args) { //创建对象c1 Chinese
c1 = new Chinese(); //创建对象c2 Chinese c2 = new Chinese(); //给对象c1的name属性赋值张三
c1.name = "张三"; //给对象c2的name属性赋值李四 c2.name = "李四"; //给对象c2的gender属性赋值女
c2.gender = '女'; //给类变量赋值 Chinese.country = "中国";//推荐 //访问各个对象的成员变量
System.out.println("c1.country = " + c1.country); //中国
System.out.println("c1.name = " + c1.name); //张三 System.out.println("c1.gender
= " + c1.gender);//男 System.out.println("c2.country = " + c2.country); //中国
System.out.println("c2.name = " + c2.name);//李四 System.out.println("c2.gender =
" + c2.gender);//女 } }
成员变量的内存图

内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。Java虚拟机要运行程序,必须要对内存进行空间的分配和管理,每一片区域都有特定的处理数据方式和内存管理方式。

详解:

下面我们使用画图的方式来分析一下为什么,类变量的值是当前类创建的所有对象共享的,而实例变量的值是每个对象独立

静态成员变量和实例变量的异同

相同点:

* 位置类中方法外
不同点:

有无static修饰

* 静态变量被static修饰
* 实例变量不能被static修饰
内存中的分数不同  

* 类变量:内存中只有一份,当前类创建的对象共享
* 实例变量:每创建一个该类对象都会开辟一份空间,来存储不同对象的实例变量。
调用方式不同

* 类变量: 可以使用对象名.属性名和类名.属性名调用,推荐使用类名.属性名调用调用
* 实例变量:对象名.属性名
生命周期不同:

* 类变量:随着类的加载而加载 随着类的消亡而消亡
* 实例变量:随着对象的创建 对象消失(当对象没有引用指向时) 被垃圾回收器回收
存储的位置不一样

* 类变量:方法区
* 实例变量:堆中
成员变量和局部变量的区别

查看下面代码
public class Car { String color;// 成员变量 // 成员方法 public void drive(String name)
{ //声明在方法上的name和定义在方法里的speed都是局部变量 int speed = 80; System.out.println("汽车正在以" +
speed + "迈的速度行驶..."); } }
成员变量和局部变量的主要区别如下:

定义的位置不同: 

* 成员变量定义在类中方法外,
* 局部变量定义在方法中或者是方法声明上
在内存中的位置不同: 

* 成员变量是在堆区,
* 局部变量是在栈区
生命周期不同:

* 成员变量是随着对象的创建而存在,随着对象的销毁而销毁
* 局部变量是随着方法的调用而存在,随着方法调用完毕而销毁
默认值不同:

* 成员变量有默认值
* 局部变量没有默认值,不赋值不能直接使用
成员变量初始化方式

* 成员变量有默认值
* 显式赋值
* 代码块
* 构造器建议只为实例变量初始化,不为静态类变量初始化
* 使用setXxx(参数列表)赋值
实例初始化

实例初始化的目的:为类中非静态成员变量赋值

实际上我们编写的代码在编译时,会自动处理代码,整理出一个<clinit>()的类初始化方法,还会整理出一个或多个的<init>(...)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。

实例初始化方法的方法体,由四部分构成:

* super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,没写,默认就是super()
* 非静态实例变量的显示赋值语句
* 非静态代码块
* 对应构造器中的代码
特别说明:其中2和3是按顺序合并的,1一定在最前面4一定在最后面

执行特点

* 创建对象时,才会执行
* 每new一个对象,都会完成该对象的实例初始化
* 调用哪个构造器,就是执行它对应的<init>实例初始化方法
* 创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)
类的初始化

类初始化的目的:为类中的静态变量进行赋值。

实际上,类初始化的过程时在调用一个<clinit>()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序
合并到类初始化<clinit>()方法体中。

* 静态类成员变量的显式赋值语句
* 静态代码块中的语句
结论:

每一个类都有一个类初始化方法<clinit>()方法,然后子类初始化时,如果发现父类加载和没有初始化,会先加载和初始化父类,然后再加载和初始化子类。
一个类,只会初始化一次。

类初始化与实例初始化

* 类初始化肯定优先于实例初始化。
* 类初始化只做一次。
* 实例初始化是每次创建对象都要进行。
类的成员之二:方法

成员变量是用来存储对象的数据信息的,那么如何表示对象的行为功能呢?就要通过方法来实现

概念:

方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元。把一个功能封装为方法的目的是,可以实现代码重用,从而简少代码量。

方法的使用原则

* 必须先声明后使用。类,变量,方法等都要先声明后使用
* 不调用不执行,调用一次执行一次。谁先调用,谁先执行
* 方法执行完毕之后,会回到方法调用处
* 方法体中的代码遵循自上而下的顺序依次逐行执行。
成员方法分为两类

* 实例方法:没有static修饰的方法,必须通过实例对象来调用。 //有参,有返回值的实例方法 public int getMax(int a,
int b) { return (a > b ? a : b); }
* 静态方法:有static修饰的方法,也叫类方法,可以由实例对象来调用也可以由类名来调用。推荐使用类名调用 //有参,有返回值的静态方法 public
static int getMax(int a, int b) { return (a > b ? a : b); }
声明方法语法格式

格式详解:

* 修饰符: 修饰符后面一一介绍,例如:public,static等都是修饰符
* 返回值类型:表示方法运行的结果的数据类型,方法执行后将结果返回到调用者。可以是基本数据类型也可以是引用数据类型,如果无返回值类型就可以使用void来代替
* 方法名:给方法起一个名字,见名知意,能准确代表该方法功能的名字
* 参数列表:方法内部需要用到其他方法中的数据,需要通过参数传递的形式将数据传递过来,可以是基本数据类型、引用数据类型。带参方法定义时,参数中的数据类型与
变量名都不能缺少,缺少任意一个程序将报错。带参方法定义时,多个参数之间使用逗号(,)分隔。参数列表也可以为空。
* 方法体:方法要完成特定功能代码
* return:结束方法,并将方法的结果返回去。如果返回值类型不是void,方法体中必须保证一定有return
返回值;语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。如果返回值类型为void时,return
后面不用跟返回值,甚至也可以没有return语句。return语句后面就不能再写其他代码了,否则会报错:Unreachable code
如何在其他类中调用方法

实例方法

类方法

public class Demo02Method { //无参,无返回值实例方法 public void method1() {
System.out.println("无参,无返回值方法"); } //无参,有返回值静态方法 public static int method2() {
return 1; } } class TestDemo02Method{ public static void main(String[] args) {
//创建对象 Demo02Method method = new Demo02Method(); //调用实例方法 method.method1();
//调用静态方法 method.method2(); //不推荐 Demo02Method.method2(); //推荐 } }
​形参和实参

* 形参:在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
* 实参:调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
总结:

* 调用时,需要传“实参”,实参的个数、类型、顺序顺序要与形参列表一一对应如果方法没有形参,就不需要也不能传实参。
*
调用时,如果方法有返回值,可以接受或处理返回值结果,当然也可以不接收,那么此时返回值就丢失了。如果方法的返回值类型是void,不需要也不能接收和处理返回值结果。

 在本类中访问本类的成员变量和成员方法

* 直接写成员变量名或者是成员方法名,不需要加“对象名."和"类名."。唯一例外:静态方法中不能直接访问本类的非静态的成员变量和成员方法 public
class Demo02Method { //无参,无返回值实例方法 public void method1() {
method2();//本类中调用其他方法 System.out.println("无参,无返回值方法"); } //无参,有返回值静态方法 public
static int method2() { //method1();错误:静态方法中不能直接访问本类的非静态的成员变量和成员方法 return 1; } }
方法调用内存分析

java 程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码 (class)文件,然后将其加载到 java 虚拟机的方法区当中,开始调用 main
方法,main 方法被调用的瞬间,会给 main 方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main 方 法的活动空间处于栈底。

也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java
虚拟机才会在“栈 内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个
方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。

由于栈的特点是先进后出, 所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main 方法最先被
调用,那么它一定是最后一个结束的。换句话说:main 方法结束了,程序也就结束了(目前 来说是这样)。

方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,
当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。

方法体当中的代码是有执行顺序的,必须遵循自上而下的顺序依次逐行执行,当前行代码必须执行结束之后,下一行代码才能执行,不能跳行执
行,还记得吗?现在再和栈数据结构一起联系起来思考一下,为什么要采用栈数据结构呢?是 不是只有采用这种先进后出的栈数据结构才可以保证代码的执行顺序呢!

查看下列代码,分析是如何执行的
package demo01; /* 1.练习: 设计一个方法可以获取两个int数的较大值,数据来自于参数 2.三要素: (1)方法名称: getMax
(2)参数列表: int a,int b (3)返回值类型: int */ public class Demo03GetMax { public static
void main(String[] args) { System.out.println("main...start..."); //调用方法:
传递的是常量 int result = getMax(100,200); System.out.println("100和200的最大值:
"+result); //调用方法方法: 传递变量 int a = 10, b = 20; int max = getMax(a,b);
System.out.println(a+"和"+b+"的最大值: "+max); System.out.println("main...end...");
} //设计一个方法可以获取两个int数的较大值,数据来自于参数 public static int getMax(int a, int b) { int
max = (a>b) ? a : b; return max;//结束方法,并且把max中的数据,返还给方法的调用处/者 } }
图解分析

方法的调用总结:

有返回值的方法调用方式
/* 有返回值的方法调用方式 1.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 ----推荐使用---- 数据类型 变量名称 =
方法名称(参数...); 2.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句 System.out.println(方法名称(参数...));
3.单独调用: 既不保存方法的结果,也没有对结果进行输出 -----不推荐使用,没有意义----- 方法名称(参数...); */ public class
Demo01DYMethod { public static void main(String[] args) {
System.out.println("main...start..."); //1.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 //数据类型
变量名称 = 方法名称(参数...); int result = getSum(10,20); //可以对结果数据做其它操作 //result *= 100;
System.out.println("和: "+result); //2.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句
//System.out.println(方法名称(参数...)); System.out.println(getSum(100,200));
//3.单独调用: 既不保存方法的结果,也没有对结果进行输出 getSum(5,10);
System.out.println("main...end..."); } //定义方法,获取2个int数字之和 public static int
getSum(int a, int b) { int sum = a + b; return sum; } }
无返回值的方法调用方式
/* 无返回值的方法调用方式 1.单独调用: 既不保存方法的结果,也没有对结果进行输出 只能采用单独调用 方法名称(参数...); 2.赋值调用:
把有返回值的方法的调用结果保存到对应的变量中 不能赋值调用 数据类型 变量名称 = 方法名称(参数...); 3.输出/打印调用:
把有返回值的方法的调用结果直接交给输出语句 不能输出调用 System.out.println(方法名称(参数...)); */ public class
Demo02DYMethod { public static void main(String[] args) {
System.out.println("main...start..."); //1.单独调用: 既不保存方法的结果,也没有对结果进行输出
//方法名称(参数...); printSum(10,20); //2.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 //数据类型 变量名称 =
方法名称(参数...); //int a =
printSum(5,15);//错误的,int变量只能保存整数,但是printSum方法执行结束没有返回任何结果数据 //void a =
printSum(5,15);//错误的,void根本不是数据类型 //3.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句
//System.out.println(方法名称(参数...)); //System.out.println(printSum(3,2));//错误:
因为printSum方法执行完毕后,没有任何结果返回 System.out.println("main...end..."); }
//定义方法,打印2个int数字之和 public static void printSum(int a, int b) { int sum = a + b;
System.out.println("和: "+sum); return ;//结束方法,返回到方法的调用处,注意没有带回任何数据 } }
方法的注意事项

* 方法不能嵌套定义(在定义方法的内部又定义了其它方法),可以调用其它方法
* 方法可以嵌套调用
* 返回值类型,必须要和 return 语句返回的数据的类型要匹配,否则编译失败 。
* 不能在 return 后面写代码, return 意味着方法结束,所有后面的代码永远不会执行,属于无效代码。
* void表示无返回值,可以省略return,也可以单独的书写return,后面不能加数据,写个分号
方法重载

概念:

* 在同一个类中,多个功能相同,但是参数列表不同的多个方法。同时存在一个类中的现象,就叫做方法重载。
作用/目的:

* 减少程序员的学习和使用成本
* 减少了方法名称的数量
JVM 调用

* 根据名称找到对应的方法
* 根据参数的数量找到对应的方法
* 根据参数的类型确定最终要调用的方法 (首先: 做类型完全匹配 其次: 完全匹配的找不到,再做自动类型提升的匹配)
方法重载与哪些因素无关

* 与参数的名称无关
* 与返回值类型无关
* 与修饰符无关
方法重载中参数列表不同有哪些情况

* 参数数量不同
* 参数类型不同
* 多个类型,顺序不同
代码示例
public class Demo { public static void main(String[] args) { method(10,10.0);
} //1.此方法只有一个int类型参数 public static void method(int a) { } //2.此方法只有两个int类型参数
//方法2和方法1参数的数量是不同的,可以构成重载 public static void method(int a,int b) { }
//3.此方法只有一个double类型参数 //方法3和方法2参数的数量是不同的,可以构成重载
//方法3和方法1参数虽然都是只有一个,但是类型不同,可以构成重载 public static void method(double a) { }
//4.此方法有一个int类型参数和一个double类型参数 public static void method(int a,double b){ }
//5.此方法有一个double类型参数和一个int类型参数 //方法5和方法4,虽然参数都是2个,但是类型的顺序不同 public static void
method(double a,int b){ } }
总结: 同类多个方法同名的前提下,。  只看多个方法的参数(除了名称以外)有区别,就构成重载

方法参数传递

前置知识:

* 使用=进行赋值的特点:把基本类型变量a的值赋值给基本类型变量b时,其实是把a中的值复制一份给变量b,之后不管如何修改变量b中的值,都不会影响变量a中的值
* 变量的作用范围:方法内部定义的变量只在所定义的方法内有效(可以使用),出了方法的作用范围,就不能使用了。
基本数据类型作为方法参数
/* 注意: 1.基本类型变量: 保存的是具体的数据值 2.基本类型变量作为形式参数,形式参数的改变,不会影响实际参数 基本类型变量作为形式参数:
定义方法时,()中定义的参数属于基本类型 不会影响实际参数: 调用方法时,()中给出的参数 */ public class Demo { public
static void main(String[] args) { int a = 10; int b = 20;
System.out.println("ms...a="+a);//10 System.out.println("ms...b="+b);//20
//调用方法 change( a , b ); System.out.println("me...a="+a);//10
System.out.println("me...b="+b);//20 } public static void change(int a, int b)
{ System.out.println("cs...a="+a);//10 System.out.println("cs...b="+b);//20 a =
a*10; b = b*10; System.out.println("ce...a="+a);//100
System.out.println("ce...b="+b);//200 } }
图解:

结论:

* 基本数据类型的参数,形式参数的改变,不影响实际参数
结论依据:

* 每个方法在栈内存中,都会有独立的栈空间,方法运行结束后就会弹栈消失
 引用数据类型作为方法参数
/* 1.引用类型变量: 保存的是对象在堆内存空间的地址值,进行参数传递的时候,传递的也是地址值
2.引用类型变量作为形式参数,通过形式参数找到对应的堆内存空间,修改堆内存空间的内容之后, 通过实际参数看到的一定是修改后的堆内存空间的内容
引用类型作为形式参数,形式参数的改变,会影响实际参数 数组: 1.数组也是一种引用类型: 数组名称保存的也是数组在堆内存空间的地址值
2.数组作为方法参数或者返回值: 传递的都是数组在堆内存空间的地址值 */ public class Demo03RefVar { public static
void main(String[] args) { int[] arr = { 10 , 20 };
//System.out.println(arr);//数组名称: 保存数组在内存中的地址值[I@1540e19d
System.out.println("ms...arr[0]="+arr[0]);//10
System.out.println("ms...arr[1]="+arr[1]);//20 //调用方法 change( arr );
System.out.println("me...arr[0]="+arr[0]);//100
System.out.println("me...arr[1]="+arr[1]);//200 } public static void
change(int[] arr ) { System.out.println("cs...arr[0]="+arr[0]);//10
System.out.println("cs...arr[1]="+arr[1]);//20 arr[0] = arr[0]*10; arr[1] =
arr[1]*10; System.out.println("ce...arr[0]="+arr[0]);//100
System.out.println("ce...arr[1]="+arr[1]);//200 } }
图解

结论:

* 对于引用类型的参数,形式参数的改变,影响实际参数的值
结论依据:

* 引用数据类型的传参,传入的是地址值,内存中会造成两个引用指向同一个内存的效果,所以即使方法弹栈,堆内存中的数据也已经是改变后的结果
可变参数

前提:在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化.

 其实可变参数本质上就是一个数组

 只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。

要求:

* 一个方法最多只能有一个可变参数,可以变参数可以接收的参数数量是0 到n 个。 /* 编译报错,因为一个方法,只能有一个可变参数 public
static void method1(int... nums,String... strs){ }*/
* 如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个 /* 编译报错,因为如果方法有多个参数,可变参数一定要放在末尾 public
static void method2(int... nums,String str){ } */
代码实例
//求n个整数的和 public class ChangeArgs { public static void main(String[] args) {
int[] arr = {1, 4, 62, 431, 2}; int sum1 = getSum1(arr);
System.out.println(sum1); //500 int sum2 = getSum2(arr);
System.out.println(sum2); //500 } // 完成数组 所有元素的求和 // 原始写法 public static int
getSum1(int[] arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { sum +=
arr[i]; } return sum; } // 可变参数写法 public static int getSum2(int... arr) { int
sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } }
递归

递归:指在当前方法内调用自己的这种现象。

递归的分类:

* 递归分为两种,直接递归和间接递归。
* 直接递归称为方法自身调用自己。
* 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:

* 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
* 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出
代码实例
package demo02; public class RecursionMethod2 { public static void
main(String[] args) { int jieCheng = jieCheng(10); System.out.println("10的阶乘是:"
+ jieCheng); //3628800 } //使用递归求阶乘 public static int jieCheng(int n) { if (n <=
1) { return 1; } return n * jieCheng(n - 1); } }

技术
今日推荐
下载桌面版
GitHub
百度网盘(提取码:draw)
Gitee
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:766591547
关注微信