1、成员变量和局部变量的区别
1、在类中的位置不同
- 成员变量:在类中方法外
- 局部变量:在方法定义中或方法声明上
2、在内存中的位置不同
- 成员变量:在堆内存,对象中
- 局部变量:在栈内存,栈帧中
3、生命周期不同
- 成员变量:随着对象的创建而存在,随着对象的消失而消失
- 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
4、初始化值不同
- 成员变量:有默认初始化值
- 局部变量:没有默认初始化值,必须定义并且赋值,然后才能使用
注意事项:
局部变量名称和成员变量名称一样,在方法中使用时采用的是就近原则。
package org.westos.demo;
public class Student {
int num = 200;
//成员变量
public void show(int a, int num) {
System.out.println(a);
//20
System.out.println(num);
//30
//成员变量和局部变量同名时,采用就近原则
}
}
package org.westos.demo;
public class MyDemo {
//成员变量
int num = 10;
public static void main(String[] args) {
//成员变量和局部变量
//局部变量:在方法定义中或方法声明上(形参)
int num = 20;
//局部变量
System.out.println(num);
//20
//当成员变量和局部变量同名,采用就近原则
Student student = new Student();
student.show(20, 30);
}
public static void testMethod(int a, int b) {
//a,b:局部变量
}
}
在Java文件中定义多个类
package org.westos.demo;
//在一个Java文件中,可以并列定义多个类,但是仅能有一个public修饰的类,并且该类名与文件名相同
public class MyTest {
public static void main(String[] args) {
System.out.println();
}
}
class Test1 {
public static void main(String[] args) {
System.out.println();
}
}
class Test2 {
private int age;
}
2、参数传递问题
Java中参数传递问题
- 形式参数
- 基本类型:值传递,形参的改变不影响实际参数
- 引用类型:类、数组、接口,引用传递(传递的是地址),形参的改变会影响实际参数
方法的参数是类名
- 如果一个方法的形参类型是一个类类型(引用类型),那么这里需要传递的实参是该类的对象。
package org.westos.demo2;
public class MyTest2 {
public static void main(String[] args) {
Person person = new Person();
showMethod(person, 10);
}
public static void showMethod(Person person, int a) {
person.num = a;
System.out.println(person.num);
//10
}
}
class Person {
int num = 20;
}
3、匿名对象
顾名思义,匿名对象,即没有名字的对象。
匿名对象的应用场景:
- 调用方法,仅仅只调用一次的情况下;(如果需要该对象调用某方法多次,则不能使用匿名对象。)
- 匿名对象可以作为实参来传递;
package org.westos.demo2;
public class MyTest {
public static void main(String[] args) {
new Student().showMethod();
//匿名对象调用方法
testMethod(new Student());
//匿名对象作为实参传递
}
public static void testMethod(Student student) {
System.out.println("匿名对象作为实参传递");
}
}
class Student {
public void showMethod() {
System.out.println("匿名对象调用方法");
}
}
4、封装与this关键字
概述:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
优势:
- 提高安全性
private关键字
- 权限修饰符的一种
- 修饰成员变量和成员方法
- 被其修饰的成员只能在本类中被访问,外界无法直接访问
private应用:
- 成员变量用private修饰
- 提供对应的getXxx()和setXxx()方法(鼠标右键,Generate,或者fn + alt + insert),可以对数据进行校验
this关键字
意义:
- 当前类的对象引用
- 谁调用这个方法,那么方法内部的this就代表谁
应用场景:
- 解决局部变量与成员变量同名时的情况
package org.westos.demo3;
public class Student {
private String name;
private int age;
//private,私有的,是一个权限修饰符,可以修饰成员变量和成员方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/*
上面的this等价参数student引用
public void setName(Student student, String name) {
student.name = name;
}
*/
public int getAge() {
return age;
}
public void setAge(int age) {
//对数据进行校验
if (age >= 0 && age < 150) {
this.age = age;
}
}
}
package org.westos.demo3;
public class MyTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
//student.setName(student, "张三");
student.setAge(210);
//student.
System.out.println(student.getAge());
//0
//age不在有效区间内,设置的是成员变量的默认值(int的默认值为0)
}
}
5、构造方法
package org.westos.demo;
public class MyTest {
public static void main(String[] args) {
//我们在创建对象时除了使用关键字new之外,还使用了Student()构造方法来完成对类的实例化
Student student = new Student();
Student student1 = new Student("张三");
System.out.println(student1.getName());
Student student2 = new Student("李四", 22);
System.out.println(student2.getName());
System.out.println(student2.getAge());
}
}
class Student {
private String name;
private int age;
/*
我们在定义一个类的时候,这个类默认存在一个空参的构造方法
构造方法:方法名和类名相同,并且没有返回值类型,连void也没有
作用:创建对象,对对象的成员变量进行初始化
*/
public Student() {
System.out.println("空参的构造方法被调用了");
}
//构造方法的重载
public Student(String name) {
//借助有参构造方法初始化成员变量
this.name = name;
System.out.println("带一个参数的构造方法被调用了");
}
public Student(String name, int age) {
this.name = name;
this.age = age;
System.out.println("带两个参数的构造方法被调用了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package org.westos.demo2;
public class Teacher {
private String name;
private int age;
//默认存在空参构造方法
public Teacher() {
System.out.println("空参构造方法被调用了");
}
//当我们写出了有参构造,默认的无参构造就没有了,如果你还想用无参构造,那么只能手动写出无参构造(无参构造显式化)
//重载构造方法
public Teacher(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造方法被调用了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Phone类
package org.westos.demo3;
public class Phone {
private String brand = "Apple";
private double price = 10000;
public Phone() {
}
public Phone(String brand, double price) {
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public void call(String name) {
System.out.println("给" + name + "打电话");
}
public void sendMessage(String name, String content) {
System.out.println("给" + name + "发短信,内容:" + content);
}
}
测试类
package org.westos.demo3;
public class MyTest {
public static void main(String[] args) {
/*
给成员变量赋值:1、setXxx()方法;2、构造方法
Phone phone = new Phone();
phone.setBrand("小米");
phone.setPrice(2000);
*/
Phone phone = new Phone("小米", 2000);
System.out.println("手机品牌:" + phone.getBrand() + ", 手机价格:" + phone.getPrice());
//手机品牌:小米, 手机价格:2000.0
phone.call("GEM");
phone.sendMessage("GEM", "该开演唱会了哈!!!");
Phone phone1 = new Phone();
System.out.println("手机品牌:" + phone1.getBrand() + ", 手机价格:" + phone1.getPrice());
//手机品牌:Apple, 手机价格:10000.0
//通过无参构造方法创建对象,成员变量存在显式初始化
}
}
6、Java对象的创建过程
当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从父类继承过来的实例变量有可能被隐藏也会分配空间)。
在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是:实例变量初始化、实例代码块初始化和构造函数初始化。
1、实例变量初始化和实例代码块初始化
我们在定义/声明实例变量的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两种方式对实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在超类构造函数的调用语句之后,(Java要求构造函数的第一条语句必须是超类构造函数的调用语句),构造函数本身的代码之前。
package org.westos.demo4;
public class InstanceVariableInitializer {
private int i = 1;
private int j = i + 1;
//j = 2
public InstanceVariableInitializer(int var) {
System.out.println(i);
//1
System.out.println(j);
//5
this.i = var;
System.out.println(i);
//8
System.out.println(j);
//5
}
{
//实例代码块
j += 3;
//j = 2 + 3 = 5
}
public static void main(String[] args) {
new InstanceVariableInitializer(8);
}
}
需要特别注意的是,Java是按照编程顺序来执行初始化,并且不允许顺序靠前的实例代码块初始化在其后面的实例变量。
public class InstanceInitializer {
{
j = i;
}
private int i = 1;
private int j;
}
public class InstanceInitializer {
private int j = i;
private int i = 1;
}
上面的这些代码都是无法通过编译的,编译器会报我们使用了一个未经定义的变量。
2、构造函数初始化
从上面可以知道,实例变量初始化和实例代码块初始化总是发生在构造函数初始化之前。
众所周知,每一个Java中的对象都至少会有一个构造函数,如果我们没有显式的定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成<init>()方法,参数列表和Java语言书写的构造函数的参数列表相同。
我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点在构造函数中是保证的:Java强制要求Object类之外的所有类的构造函数的第一条语句必须是超类构造函数的调用语句或者类中定义的其他构造函数,如果我们既没有调用其他构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用。
public class ConstructorExample {
}
对于这个类,没有extends关键字,那么父类为Object类,我们观察编译之后的字节码会发现编译器为我们生成了一个构造函数,如下
aload_0
invokespecial #8; //Method java/lang/Object."<init>":()V
return
上面的第二行就是调用Object类的默认构造函数的指令。
如果我们显式调用父类的构造函数那么该调用必须放在构造函数所有代码的前面,也就是构造函数的第一条指令。
特别的,如果我们在一个构造函数中调用另外一个构造函数,
public class ConstructorExample {
private int i;
public ConstructorExample() {
this(1);
....
}
public ConstructorExample(int i) {
....
this.i = i;
....
}
}
对于这种情况,Java只允许在ConstructorExample(int i)内调用超类的构造函数。
下面的两种情况,编译不能通过。
public class ConstructorExample {
private int i;
ConstructorExample() {
super();
this(1); // Error:Constructor call must be the first statement in a constructor
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
public class ConstructorExample {
private int i;
ConstructorExample() {
this(1);
super(); //Error: Constructor call must be the first statement in a constructor
....
}
ConstructorExample(int i) {
this.i = i;
}
}
对于一个构造函数,要么显式调用父类的构造函数,要么调用本类其他构造函数。不能两者同时存在。
3、小结
在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。此时,首先实例化Object类,再依次对以下各类进行实例化,直到完成对目标类的实例化。
具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。