1、面向对象概念
1.1、理解面向对象
2、类与对象的关系
现实生活中的对象:张三、李四。
描述:提取对象中共性内容,既对具体的抽象。描述时,这些对象的共性有:姓名,年龄,性别,学习java功能。
映射到java中,描述就是class定义的类,具体对象就是对应java在堆内存中用new建立实体。
总结就是:
类:对现实生活中事物抽象的描述。
对象:就是这类事物,实实在在存在个体。
(凡是用于存储多个数据的,我们称之为实体,都放在堆内存中,比如数组)
2.1、类与对象关系图
2.2、类的定义
3.3、成员变量与局部变量的区别
注意:
Class Car{
String color = "红色"
}
Class CarDemo{
public static void main(String[] args){
Car c = new Car();
c.color = "蓝色";
Car c1 = new Car();
System.out.println(c1.color);
//这里输出红色,因为c只改变了其创建的对象的颜色,并不会改变原来类中的初始化值
}
}
2.4、创建对象与使用对象
多个引用可以指向一个对象,比如
Car c = new Car();
Car c1 = c;
这样对象c,c1都指向new出来的对象。
2.5、对象内存结构
2.6、匿名对象
具体参考:匿名对象
对于下面这篇文章的代码
/*
匿名对象
匿名信
修黑车
汽车类
黑车厂类
把汽车的改成黑色3轮车。
*/
class Car {
String name = "smart";
String color = "red";
int num = 4;
void run() {
System.out.println(name + ":" + color + ":" + num + ":跑起来了。。。。");
}
}
class BlackCarFactory {
String name;
String addr;
//在改装车的过程中,我们想要改变车的属性,常规必须创建Car的对象,通过对象改变
//我们在这里直接创建Car类的方法repairCar,将Car类的对象c直接作为方法的形参,
//这样,我们便可以在下面的主函数中通过赋予repairCar方法Car类的对象,
//直接调用repairCar方法来对汽车进行改装,这样便优化了代码
//注意,该repairCar方法返回值是Car类型,形参是Car类型的对象
Car repairCar(Car c) {
c.num = 3;
c.color = "黑色";
System.out.println("改装成功啦。。。");
return c;//注意带返回值
}
}
class Demo1 {
public static void main(String[] args) {
BlackCarFactory bcf = new BlackCarFactory();
bcf.name = "幸福修理厂";
bcf.addr = "天河区棠东东路御富科贸园a栋206";
// 非匿名对象
Car c = new Car();
c.run();
// 改装
//其实这里对应的是返回值为void类型的repairCar()方法
bcf.repairCar(c);//通过传递Car类对象的方法对车进行改装
// 取车
c.run();//改装后再使用车
//需要注意的是,这里只是针对某一个new出来的对象(某一辆车)的参数进行修改
//原来Car类的属性并没有改变
Car c3 = new Car();
c3.run();
//结果任然是:smart:red:4:跑起来了。。。。
// 匿名对象一,只使用一次:
// 如下创建了2个对象
/*
* new Car().run();
*
* new Car().run();
*/
// 匿名对象二,作为实际参数传递
//这里直接将newCar()赋予repairCar方法,完成Car类参数的改变(既改装车的过程)
//由于repairCar方法的返回值类型是Car类型,那么可以直接将其赋予Car类型的变量c2
Car c2 = bcf.repairCar(new Car());
//这一句与Car c = new Car();bcf.repairCar(c);功能是一样的,但是这里要用返回值为void类型的repairCar()方法
//c2再运行的时候就是改装后的参数
c2.run();
System.out.println();
}
}
3、封装
下面介绍一下通过private关键字的私有化
class Person {
private int age;
//一个私有化的成员变量通常会对应2个访问方式,一个一个设置一个获取
public void setAge(int a){
if(age>0 && age<130){
//注意,此处a的值是赋予下面p对象中的的age,并不是赋予成员变量private的age
age = a;
}else{
System.out.println("非法输入");
}
}
public int getAge(){
return age;
}
void speak(){
System. out. print1n("age="+age);
}
}
class personDemo
{
public static void main(string[] args)
Person p=new Person();
//由于age可以直接被访问,导致用户可能赋予其错误的参数
//我们用private修饰age,使其只在Person类中有效,Person类以外即使建立了对象也无法直接访问,所以我们必须在Person类中提供访问age的方式
//之所以对外提供访问方式,就因为可以在访问方式中加入逻辑判断等语句。对访问的数据进行操作。提高代码健壮性。如上面对age不小于0的判断
p. age=-20;
p. speak();
}
}
修饰符的作用域:添加链接描述
注意:私有仅仅是封装的一种表现形式。养成好习惯,以后在写代码的时候,类里面有多少成员属性,都应该将其隐藏起来,提供setting方法与getting方法给外界访问。当然,有的属性是不需要隐藏的。
4、构造函数
例子1:
class Demo1 {
public static void main(String[] args) {
Person p = new Person();
}
}
class Person{
Person(){
System.out.println("Person run");
}
}
上述代码运行结果为:
Person run
说明一个类的对象一旦建立,便会调用其构造函数。也就是new一次调用一次构造函数。也就是,new一次就会调用构造函数对对象进行初始化。
当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。也就是:Person(){}。当在类中自定义了构造函数后(不管是无参数还是有参数的构造函数),默认的构造函数就没有了,如下面例子2,如果我们将第一个空参数的构造函数注释,其他构造函数不注释,那么Person p1 = new Person(); 这一句就会报错,因为我们自己构造了构造函数,系统不会自己为 new Person(); 这一类型的对象创建构造函数。
例子2:
class Demo1 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person("lisi");
Person p3 = new Person("wnagu",10);
}
}
class Person{
private String name;
private int age;
/*
构造代码块
作用:给对象进行初始化。对象一建立就运行,而且优先于构造函数执行。
和构造函数的区别:
构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。|
基于构造代码块可以给所有对象统一初始化,它可以用来定义对象共性内容
*/
{
System.out.println("person code run");
//比如如果所有的对象都调用cry()方法,那么只需要将这个方法放在构造代码块里面即可~
cry();
}
//由于不同的对象需要用不同的构造函数进行初始化
//因此,我们通过构造函数的重载方法,可以对不同的对象进行初始化
Person(){
System.out.println("A:name="+name+",age="+age);
// cry();
}
Person(String n){
name = n;
System.out.println("B:name="+name+",age="+age);
// cry();
}
Person(String n,int a){
age = a;
name = n;
System.out.println("C:name="+name+",age="+age);
// cry();
}
public void cry() {
System.out.println("cry");
}
}
上述代码运行结果为:
person code run
cry
A:name=null,age=0
person code run
cry
B:name=lisi,age=0
person code run
cry
C:name=wnagu,age=10
//说明对不同的对象,我们应该建立不同的构造函数进行初始化
//另一方面,构造代码块在每次创建对象的时候都会运行,且优先于构造函数。
构造函数和一般函数在写法上有不同,在运行上也有不同。构造函数是在对象一建立就运行,给对象初始化;而一般方法是对象调用才执行,给是对象添加对象具备的功能。一个对象建立,构造函数只运行一次。而一般方法可以被该对象调用多次。
有了构造函数,setting与getting方法也要继续写,因为后续我们也可能修改以及调用成员变量。
什么时候定义构造函数呢?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。
5、this关键字
注意this与对象相联系,这是最关键的!!!
特点:this代表其所在函数所属对象的引用。换言之:哪个对象在调用this所在的函数,this就代表哪个对象。
什么时候使用this关键字呢?当在函数内需要用到调用该函数的对象时,就用this。在Java中this可以完成三件事情:调用本类属性(包括static类变量)、调用本类方法、表示对当前对象的引用。
例子1:调用本类属性
public class Demo2 {
public static void main(String[] args) {
Person obj = new Person("张三",23);
System.out.println(obj.getInfo());
}
}
class Person{
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public String getInfo() {
return "姓名:"+this.name+" 年龄:"+this.age;
}
}
结果是:
姓名:张三 年龄:23
如上,用this来引用本类的成员变量,注意,以后只要是调用本类中的属性,都使用“this.属性”的方式来进行,不用“本类对象.属性”,这样一看就知道this引用的是本类的成员变量。
注意,this可以引用本类的成员变量中的类变量(成员变量包括实例变量与类变量,实例变量就是普通变量):this.类变量(静态变量),因为类变量本类就是类一加载就加载到内存,this 代表本类的对象,对象当然可以引用类变量,但是一般类变量在本类中可以直接使用,一般不需要加this。但是注意this不能用在static 方法中。(总结就是静态方法中不能直接使用非静态的方法与变量,必须创建实例后才能通过实例调用非静态方法与变量;而非静态方法中可以直接使用静态变量与方法。这是因为类变量与方法比实例变量与方法要先加载)
例子2:调用本类方法
一个类中的方法分为两种:
(1)普通方法:如果现在要调用的是本类方法,可以使用“this.方法()”调用,当然this也可以调用父类的方法;
(2)构造方法:调用构造方法使用“this(参数…)”调用。
public class Demo2 {
public static void main(String[] args) {
Person obj = new Person("张三",23);
System.out.println(obj.getInfo());
}
}
class Person{
private String name;
private int age;
public Person() {
System.out.println("A:一个新的String对象被实例化");
}
public Person(String name) {
System.out.println("B:一个新的String对象被实例化");
this.name = name;
}
public Person(String name,int age) {
System.out.println("C:一个新的String对象被实例化");
this.name = name;
this.age = age;
}
public String getInfo() {
return "姓名:"+this.name+" 年龄:"+this.age;
}
}
此时的程序之中出现了大量的重复代码,而我们的目标是尽量没有重复。这种情况下就可以利用this()来完成。
public class Demo2 {
public static void main(String[] args) {
Person obj1 = new Person();
System.out.println(obj1.getInfo());
Person obj2 = new Person("张三");
System.out.println(obj2.getInfo());
Person obj3 = new Person("张三",23);
System.out.println(obj3.getInfo());
}
}
class Person{
private String name;
private int age;
public Person() {
System.out.println("A:一个新的String对象被实例化");
}
public Person(String name) {
this();//调用无参数的构造函数
this.name = name;
}
public Person(String name,int age) {
this(name);//调用有一个参数的构造函数
this.age = age;
}
public String getInfo() {
return "姓名:"+this.name+" 年龄:"+this.age;
}
}
结果是
A:一个新的String对象被实例化
姓名:null 年龄:0
A:一个新的String对象被实例化
姓名:张三 年龄:0
A:一个新的String对象被实例化
姓名:张三 年龄:23
解析:使用this一层层地调用构造方法,这样便省去很多代码。
所有的构造方法是在对象实例化的时候被默认调用,而且是在调用普通方法之前调用.使用“this()”调用构造方法的操作,一定要放在构造方法的首行,因为初始化动作必须先执行;
如果一个类之中存在了多个构造方法的话,并且这些构造方法都使用了this()互相调用,那么至少要保留一个构造方法没有调用其他构造,以作为程序的出口。(见云笔记相关代码)
例子3:this表示当前对象
当前对象,是指当前正在调用类中的对象。先看如下代码:
public class Demo2 {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println(p1);
Person p2 = new Person();
System.out.println(p2);
}
}
class Person{
}
结果是
Person@7852e922
Person@4e25154f
在上述代码中加一个this
public class Demo2 {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println(p1);
p1.print();
Person p2 = new Person();
System.out.println(p2);
p2.print();
}
}
class Person{
public void print() {
//this就是指当前正在调用类的对象
System.out.println("this="+this);
}
}
结果是
Person@7852e922
this=Person@7852e922
Person@4e25154f
this=Person@4e25154f
也就是说,this相当于类的一个对象,所以上面可以用this来引用成员属性。上面结果输出对象的哈希码。
this应用举例:
/*
* 需求:给人定义一个用于比较年龄是否相同的功能。也就是是否是同龄人。
*/
public class Demo2 {
public static void main(String[] args) {
Person p1 = new Person(20);
Person p2 = new Person(25);
boolean answer = p1.compare(p2);
System.out.println("两人年龄相同:"+answer);
}
}
class Person{
private String name;
private int age;
public Person(int age) {
this.age = age;
}
//这里比较的是2个人的年龄,我们不用int age作为形参,而将Person p作为形参,
//这样也方便调用Person的其他参数(比如后面如果想比较姓名,也可以直接用p调用)
public boolean compare(Person p) {
//这里,我们用this来指代调用包含this的compare方法的对象p1
//p代表的是传递进来的参数p2
return this.age == p.age;
}
}
this的应用:当定义类中函数时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象。就是哪个对象后期调用该函数,this就代表哪个对象。但凡本类功能内部使用到了本类对象,都用this表示。
6、static关键字
注意static与类相联系,这是最关键的!!!特有的方法或属性不用static,因为其与对象相关。而共有的方法或属性则应设置为static,其与类相关 。定义过多的静态变量会占用太多内存。
java中使用static关键字修饰的常量、成员变量或成员方法为静态常量,变量或方法。静态成员属于类所有,可以在本类或者其他类中使用类名和“.”来调用静态成员。格式:类名.静态成员,也可以使用对象来调用静态成员,但是为了避免混淆,我们一般不这样操作。(注意,非静态成员用对象.非静态成员调用)
使用:静态数据与方法的作用通常是为了提供共享数据或者方法,便于各类调用该静态成员而不需要多次创建占用内存(否则如果我们有多个对象要调用该成员,就要在堆内存里面创建多个空间,这样就比较消耗资源)。 静态成员可以被其他类调用,但同样遵循public、protected以及private修饰符的约束。
静态成员优先于对象存在,并且能被对象所共享。
例子1:
public class Demo {
int pi = 1;//非静态成员变量
static int po = 3;//静态成员变量
public int method() {//非静态方法
return 2;
}
public static int method2() {//静态方法
return 4;
}
public static void main(String[] args) {
Demo obj = new Demo();
System.out.println("pi="+obj.pi);//用对象调用非静态成员变量
System.out.println("method()返回值为:"+obj.method());//用对象调用非静态成员方法
Demo.po = 5;//静态变量可通过类名调用后再将其修改
System.out.println("po="+Demo.po);//用类名调用静态成员变量
System.out.println("method2()返回值为:"+Demo.method2());//用类名调用静态成员方法
}
}
注意:
(1)静态方法中不可以使用this、super关键字,因为静态优先于对象存在,this代表对象,因此不可以;
(2)静态方法中可以直接调用同类中的静态成员,但不能直接调用非静态成员,如果希望在静态方法中调用非静态成员,可以通过创建类的对象,然后通过对象来访问非静态成员。另外,非静态方法则可以直接通过类名调用静态成员,同样,非静态方法想要调用其他非静态成员,就可以直接调用。
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
(3)不可以将局部变量声明为静态static。因为静态成员属于整个类,当系统第一次使用该类时,就会为其分配内存空间直到该类被卸载才会进行资源回收!
加料:static内存分布
首先明确,由static定义成员存储在内存的方法区(也叫共享区、数据区)。类中的方法、共享数据都在方法区。
被static修饰的成员会随着类的加载而加载。意思是:当类加载进内存的时候,被static修饰的静态成员就已经在内存中开辟好了空间。当然,被static修饰的成员也会随着类的消失而消失。
如在下面这段代码中,类Person加载到内存中的时候,静态成员country已经加载,而非静态成员name则等到创建了对象才会加载到内存。
public class Demo {
public static void main(String[] args) {
}
}
class Person{
String name;//成员变量,实例变量(实例也就是对象)
static String country = "CN";//静态的成员变量。类变量
public void show() {
System.out.println(name+":"+country);
}
}
实例变量和类变量的区别:
1、存放位置:类变量随着类的加载而存在于方法区中。实例变量随着对象的建立而存在于堆内存中。
2、生命周期:类变量生命周期最长,随着类的消失而消失。实例变量生命周期随着对象的消失而消失。
再看下面这段代码:
public class Demo {
public static void main(String[] args) {
//我们在此处直接用Person类调用静态的show()方法,但是show()方法中包含非静态的name
//因此这段程序会报错,静态的方法没办法直接访问非静态成员,只能通过对象访问
Person.show();
}
}
class Person{
String name;
static String country = "CN";
public static void show() {
System.out.println(name+":"+country);
}
}
再考虑什么时候使用静态
/*
什么使用静态?要从两方面下手:因为静态修饰的内容有成员变量和函数。
什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰。对象中的特有数据要定义成非静态存在于堆内存中。(如同一个学校的学生,学校这个属性就是共享数据,而各个学生的姓名就是特有数据)
什么时候定义静态函数呢?
当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
因为如果静态方法访问到非静态数据,则需要创建该数据所在类的对象来访问,很麻烦!
*/
class Person{
String name;
public static void show() {
//这个方法并没有访问到非静态变量,既没有操作到对象的特有数据,那么可以将其设置为static
//否则其他方法在访问show方法的时候总是需要创建Person类的对象,这样就显得多余
System.out.println("haha");
}
}
public class Demo {
public static void main(String[] args) {
Person.show()//这样就可以直接调用show()方法
}
}
静态方法的内存分析-就业班day08-14,静态内存的分析。
7、静态的应用
//先看看例子1
public class Demo {
public static void main(String[] args) {
int[] arr = {3,4,5,6};
int max = Demo.getMax(arr);
System.out.println(max);
}
public static int getMax(int[] arr) {
int max = 0;
for(int x=1; x<arr.length ;x++) {
if(arr[x]>arr[max])
max = x;
}
return arr[max];
}
}
//有可能其他类的其他数组也有获取最大值的需求,我们干脆将数组的功能封装到一个类中,便于使用
封装后如下:
//每一个应用程序中都有共性的功能,可以将这些功能进行抽取,独立封装,以便复用。
下面看看例子2
public class Demo {
public static void main(String[] args) {
int[] arr = {3,4,5,6};
// ArrayTool tool = new ArrayTool();
// int max = tool.getMax(arr);
// System.out.println(max);
/*
* 虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作,发现了问题:
* 1,对象是用于封装数据的,可是ArrayTool对象并未封装特有数据。
* 2,操作数组的每一个方法都没有用到ArrayTool对象中的特有数据。
* 这时就考虑,让程序更严谨,是不需要对象的。
* 可以将ArrayToo1中的方法都定义成static的。直接通过类名调用即可。
*/
int max = ArrayTool.getMax(arr);//这里便不需要创建对象
//通常情况下工具类里面定义的都是静态方法。
}
}
class ArrayTool{//工具类
//将方法都静态后,可以方便于使用,但是该类还是可以被其他程序建立对象的。
//为了更为严谨,强制让该类不能建立对象,可以通过将构造函数私有化完成。
private ArrayTool() {}
//直接将工具类的构造函数私有化,这样子其他类便没办法创建该类的对象,直接通过类名访问静态方法与变量,避免调用者程序里出现莫名其妙的多余对象
//获取最大值的方法
public static int getMax(int[] arr) {
int max = 0;
for(int x=1; x<arr.length ;x++) {
if(arr[x]>arr[max])
max = x;
}
return arr[max];
}
//获取最小值的方法
public static int getMin(int[] arr) {
int min = 0;
for(int x=1; x<arr.length ;x++) {
if(arr[x]<arr[min])
min = x;
}
return arr[min];
}
//冒泡排序的方法
public static int[] bubbleSort(int[] arr) {
for(int i=1; i<arr.length ;i++) {
for(int j=0; j<arr.length-i ;j++) {
if(arr[j]>arr[j+1])
swap(arr[j],arr[j+1]);
}
}
return arr;
}
//交换两个数值的方法,该方法不给外界使用,直接私有化隐藏,能隐藏的方法就隐藏起来
private static void swap(int num1,int num2) {
int temp = num1;
num1 = num2;
num2 = temp;
}
}
8、main函数
/*
public static void main(string[] args)
主函数;是一个特殊的函数。作为程序的入口,可以被jvm调用。
主函数的定义:
public:代表着该函数访问权限是最大的。
static:代表主函数随着类的加载就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(string[] arr):函数的参数,参数类型是一个数组,该数组中的元素是字符串。字符串类型的数组。
主函数是固定格式的:jvm识别。
*/
public class Demo {
public static void main(String[] args) {
System.out.println(args);
System.out.println(args.length);
}
}
结果是:
[Ljava.lang.String;@7852e922
0
//jvm在调用主函数时,传入的是new string[0],既传入一个长度为0的字符串类型的数组
玩一下
public class Demo {
public static void main(String[] args) {
String[] arr = {"haha","heihei","huhu","xixi"};
MainTest.main(arr);
}
}
class MainTest{
public static void main(String[] args) {
for(int x=0; x<args.length ;x++)
System.out.println(args[x]);
}
}
//一般开发中多个类只设定一个主函数,避免混淆
9、面向对象补充
静态代码块、代码块、构造方法在子父类中的执行顺序
一个对象的内存分析(就业班-day06-07视频解析,这部分很重要!)。
在创建对象的时候,对于成员变量,虚拟机参照方法区中的类信息中关于成员变量的信息,在堆中创建相应的成员变量,这些成员变量也保存了相应的值;而对于成员方法,在创建对象的时候是将方法区中的成员方法的地址值赋予堆中的成员方法,也就是堆中并没有保存真正的成员方法,而只是指向方法区中相应成员方法的地址值。
总结一下:
1、成员变量在方法区的类信息中,当创建对象的时候,会在堆中参考方法区创建相应的成员变量,成员变量的值也在堆中;局部变量在栈中,局部变量的值保存在堆中,局部变量会指向其在堆中值的地址。
2、方法的信息保存在方法区的类信息中。当创建对象的时候,会在堆中创建一个方法,但是这个方法并不是没有真正的方法信息,而只是指向方法区中所对应的方法地址的一个引用。方法在使用的时候都需要先进栈,最先进栈的是main方法,接着我们在main中创建对象调用成员方法,main中的其他方法会相应进栈。进出栈的形式是先进后出,因此按代码顺序执行,执行完毕的方法会出栈,接着下一个方法继续进栈,直到main里面的方法全部执行完毕,main方法也出栈。
另一方面,在调用成员方法的时候,成员方法想要运行,也需要进栈。注意视频就业班-day06-7里面对于成员方法进栈(压栈)与出栈(弹栈)的分析。
两个对象的内存分析(就业班-day06-08视频解析,这部分很重要!)。
两个对象引用指向同一个对象的内存分析(就业班-day06-09视频解析,这部分很重要!)。
使用对象作为方法参数的内存分析(就业班-day06-10视频解析,这部分很重要!)。
当一个对象作为参数,传递到方法当中时,实际上传递进去的是对象的地址值。
使用对象作为方法返回值的内存分析(就业班-day06-11视频解析,这部分很重要!)。
当使用一个对象类型作为方法的返回值时:返回值其实就是对象的地址值。
注意,Boolean类型的getter方法不太一样,应该是is…,不是get…
private boolean male; // 是不是男
//setter方法
public void setMale(boolean b) {
male = b;
}
//对于基本类型当中的boolean值,Getter方法一定要写成isXxx的形式,而setXxx规则不变。
public boolean isMale() {
return male;
}
如何定义一个标准的类——(就业班-day06-18视频解析)。
IDEA快速生成getter与setter的方法——先写成员变量,然后光标移到下面,**点击code菜单-Generate(快捷键alt+insert)-选择getter与setter方法即可。**对于构造方法等代码也可以这样操作。
final关键字补充
/*
final关键字代表最终、不可改变的。
常见四种用法:
1. 可以用来修饰一个类
2. 可以用来修饰一个方法
3. 还可以用来修饰一个局部变量
4. 还可以用来修饰一个成员变量
*/
// 对于基本类型来说,不可变说的是变量当中的数据不可改变
// 对于引用类型来说,不可变说的是变量当中的地址值不可改变,但是引用对象里面变量的值可以改变
/*
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。(因为我们至少要调用一个重载的构造方法,如果没有全部赋值,一当调用到那个没有赋值的构造方法,那么成员变量就没有赋值到!)
*/