java面向对象

面向对象

概述

万物皆对象。面向对象,是一种编程思想,是把一个整体的事物按照各个功能来进行划分。在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。 它区别于面向过程思想,强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。

面向过程
  • 概述:需求(要做什么)——分析(具体怎么做)——实现(代码体现),整个过程中的每一个步骤都需要我们去操作和实现。
  • 面向过程开发:就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成后,由这些功能方法的相互调用,完成需求。
  • 面向过程,强调的是每一个功能的步骤
  • 面向过程的代表语言:C语言
面向对象

概述:面向对象是基于面向过程的编程思想。

面向对象,强调的是对象,然后由对象去调用功能,它只关注结果
面向对象开发:不断地创建对象,使用对象,指挥对象做事情。

面向对象设计:在管理和维护对象之间的关系。

面向对象特征:

  • 封装(encapsulation)
  • 继承(inheritance)
  • 多态(polymorphism)

例如洗衣服:

  • 面向过程:把衣服脱下来–>找一个盆–>放点洗衣粉–>加点水–>浸泡10分钟–>揉一揉–>清洗衣服–>拧干–>晾
    起来
  • 面向对象:把衣服脱下来–>打开全自动洗衣机–>扔衣服–>按钮–>晾起来

区别:

  • 面向过程:分析解决问题的步骤,按照设定好的步骤逐步执行,强调步骤
  • 面向对象:分析对象的行为和特征,将每个行为当做一个整体,按照需求进行组合替换,强调的是对象。

例如蛋炒饭与盖浇饭:

蛋炒饭就是饭和蛋一起搅拌均匀,味道均匀,但是如果想要换菜,就需要全部倒掉,重新再做,浪费时间和资源

盖饭就是在饭上面浇灌各种菜,分别做好饭和菜,想吃那种菜就加哪种,如果不想要已经加上的菜,可以倒掉那份菜,换另一种。方便换口味,但是味道不够均匀

面向对象和面向过程的优缺点:

  • 面向过程:性能较高,但是高耦合,不易于维护、扩展、复用
  • 面向对象:低耦合,使用灵活便于维护,复用性强、可扩展性高,但是性能比不上面向过程,因为调用时需要实例化,需要资源

特点:

面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,我们可以不用管它具体的实现步骤,将我们从执行者变成了指挥者。

一、类

(一)概述

  • Java语言最基本的单位是类,我们将现实世界的事物用一个类来体现。
  • 类就是具有相同特征和行为的集合,可以看成是一类事物的模板,是对现实事物的一种抽象描述(如:人、汽车等)
  • 对象就是类的实例化,即实际存在的对象、具体的个体(人类中的小明、奔驰汽车等)
  • 类的组成
    • 属性:指事物的特征,(对象静态特征),例如:小明的眼睛、鼻子等
    • 行为:指事物能执行的操作,具有独立功能的代码块组成的整体,使其具有特殊功能的代码集,他是对象的动态行为(即对象的行为,如小明走路、吃饭等)
  • 类和对象的关系
    • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象描述,是抽象的
    • 对象:是看得到摸的着的真实存在的实体,是一类事物中具体的个体,是具体的、真实存在的
      简单理解:类是对事物的一种描述,对象则为具体存在的事物,即类是对象的模板,对象是类的实体

(二)类的定义

类由属性与行为两部分构成:

  • 属性:在类中体现为成员变量
  • 行为:在类中体现为成员方法

成员变量:定义在类中,在方法体外面的变量

成员方法:对应事物的行为,不能使用static修饰

类的定义步骤:
①定义类
②编写类的成员变量
③编写类的成员方法

public class 类名 {    
    // 成员变量    
    变量1的数据类型 变量1;    
    变量2的数据类型 变量2;// 成员方法 实现功能的代码
    方法1;   
    方法2;
}

示例:

public class test {
    //成员变量
    String name;
    int age;
    //成员方法
    public void call() {
         System.out.println(name + "正在打电话");
    }
    public void message() {
         System.out.println(name + "今年"+ age + "岁了");
    }
}

(三)对象

初始化:为元素分配内存空间,并为每个元素赋初始值

引用实际上就是指针。但它是安全的,可控的

创建对象:

类名 对象名 = new 类名(); 
new 类名(); // 可以不添加对象名,此时是匿名对象

匿名对象应用场景:

(1)对象调用方法仅仅一次的时候

格式:new 类名().方法名(参数...);

好处:匿名对象调用方法后就是垃圾,可以被垃圾回收器回收,从而提高内存的使用效率。

(2)匿名对象可以作为实际参数传递

格式:方法名(new 类名());

注意:匿名对象访问成员变量意义不大,一般不使用。

使用对象访问类中的成员:

对象名.成员变量             对象名.成员方法(参数...);

示例:

public class Test {
    //成员变量
    String name;
    int age;
    //成员方法
    public void call() {
        System.out.println(name + "正在打电话");
    }
    public void message() {
        System.out.println(name + "今年" + age + "岁了");
    }
    public static void main(String[] args) {
        Test t = new Test();
        // 调用成员变量
        t.name = "小明";
        t.age = 18;
        // 调用成员方法
        t.call();
        t.message();
        // 若不需要对象名,也可以直接使用对象
        new Test().message();
    }
}

成员变量的默认值

数据变量默认值
基本类型整数(byte,short,int,long)0
浮点数(float,double)0.0
字符(char)\u0000
布尔(boolean)false
引用类型数组、类、接口null

注意:

只有成员变量或者类变量(static修饰的变量)才有默认值,局部变量没有默认值

对象创建过程

创建对象的步骤:

1)加载类信息,只加载一次

2)在堆中给对象分配空间

3)对象初始化操作

​ a. 默认初始化(赋予默认值:整数:0;小数0.0;字符:\u0000;布尔:false;引用:null);

​ b. 显示初始化(即赋给成员声明时给到的值)

​ c. 构造器初始化(把传入构造器的值重新赋给属性,当调用无参构造器时不赋值)

4)返回对象的地址给对象引用;

对象内存图
  • 单个数组

请添加图片描述

  • 单个数组修改数据

请添加图片描述

  • 多个数组

请添加图片描述

  • 多个数组指向同个地址

请添加图片描述

  • 方法执行过程

请添加图片描述

  • this内存原理

请添加图片描述

总结

  • 当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)。只要有任何一个对象修改了内存中的数据,此后无论使用哪一个对象进行数据获取,都是修改后的数据。

(四)方法

概述:方法是用来实现一个类中某个功能的一段代码

方法的作用:

  • 减少代码冗余
  • 提高复用性
  • 提高可读性
  • 提高可维护性
  • 方便分工合作

使用经验:一个方法只做一件事,即一个方法只实现一个功能

1、方法的通用格式
public [static] 返回值类型 方法名(参数){
    方法体; // 完成某种特定功能的代码
    // 当返回类型为void时,return可以不写,或者return后面直接加分号结束
    // return:用来结束方法的
    return 返回值; // 返回值:功能的返回结果,由return返回给方法的调用者
}

注意:

定义方法时,要做到两个明确

  • 明确返回值类型:明确方法操作完毕后是否有返回值,有则写要返回数据的对应数据类型,无则写void
  • 明确参数:明确参数类型和数量

调用方法时:

  • void类型的方法,直接调用即可
  • 非void类型的方法。推荐用变量接收调用
2、参数类型

作用:方法的参数可以让代码功能更灵活、普适性更高,易于修改及维护

参数有形参和实参,定义方法时写的参数叫形参,相当于局部变量的声明,真正调用方法时,传递的参数叫实参,相当于局部变量的赋值。

  • 形参:用来接收方法被调用时传递的参数。只有在被调用的时候才分配内存空间,一旦调用结束,就释放内存空间。因此仅仅在方法内有效。
  • 实参:调用中的形参,即使用状态下的形参,传递给被调用方法的值,预先创建并赋予确定值。

在这里插入图片描述

不定长参数

就是不限定参数的长度,即可变参数。不定长参数可以理解为数组,使用的时候也当做数组使用

格式:
返回值 方法名(参数类型...参数名)
void s(int... a) // 相当于void s(int [] a)
使用要求:必须放在参数列表的最后面
void s(double d, int... a) // 相当于void s(double d, int [] a)

注意事项和使用细节:

  • 可变参数的实参可以为0个或任意多个。
  • 可变参数的实参可以为数组。
  • 可变参数的本质就是数组。
  • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后。
  • 一个形参列表中只能出现一个可变参数。
方法返回值

概念:方法执行后返回的结果

根据需求确定方法有无返回值,返回值类型是什么

什么时候使用teturn语句?

1. 需要一个返回值
2. 符合提前结束方法条件时返回
方法调用
无返回值的方法

直接调用:直接写方法名调用(只有这种调用方式)

public static void main(String[] args) {
	print(); // 直接调用
}
public static void print() {
	System.out.println("方法被调用");
}
有返回值的方法

直接调用:直接写方法名调用

	public static void main(String[] args){
		add(5,10);	// 一般来说没有意义,所以不推荐
	}
	public static int add(int num1 , int num2){
		int sum = num1 + num2;
		return sum;
	}

输出语句调用:

在输出语句中调用方法, System.out.println(方法名()) 。

作用不是很大,因为我们不一定非要把返回结果输出,我们可能还需要对返回结果进行进一步的操作。

public static void main(String[] args) {
	System.out.println(getSum(5,6));
}
public static int getSum(int a,int b) {
	return a + b;
}

赋值调用

调用方法,在方法前面定义变量,接收方法返回值

推荐,因为我们可以使用该变量做其他运算

public static void main(String[] args){
    int result = add(5,10);// int result = 15;
    System.out.println(result);
    //在计算了5+10的总和之后,继续与20进行相加求和
    int result2 = add( result , 20);
    System.out.println(result2);
}
public static int add(int num1 , int num2){
    int sum = num1 + num2;
    return sum;
}
return关键字

在这里插入图片描述

在这里插入图片描述

return的三种用法:

  • 应用在具有返回值类型的方法中:
    return value; 表示结束当前方法,并伴有返回值,返回到方法调用处。
  • 应用在没有返回值类型(void)的方法中:
    return; 表示结束当前方法,直接返回到方法调用处。
  • 在判断语句块中,在符合条件时表示结束当前方法,并返回数据

在这里插入图片描述

java传递参数

Java传递参数是值传递,所以基本数据类型传值是直接把表达式的值复制给参数,按照引用类型说,就是有两个实体对象,所以你改动其中一个值另一个并不会改变。引用类型传值就是传递引用变量中保存的内存地址值,即它不是直接复制实体对象,而是复制引用变量中的地址,两个引用地址都指向同一个实体对象,操作一个引用在实体对象里面对对象的属性做了改动,另一个引用也能发现这种改动,但是引用本身并不会发生改变,所以引用传值改变的是对象的属性,而不是引用本身。

参考:方法传参

方法参数传递(基本数据类型):形参的改变不影响实参的值
 public static void main(String[] args) {
        /**
         * 输出:
         * 方法调用前:100
         * 方法调用后:100
         */
        int number = 100;
        System.out.println("方法调用前:" + number );
        change(number);
        System.out.println("方法调用后:" + number);
    }
     static void change(int number){
        number = 200;
    }

在这里插入图片描述

方法参数传递(引用数据类型)
  • 将实参的引用类型的值(即在堆空间中生成的首地址的值)传递给形参的引用类型的变量
  • 形参方法内的参数值改变时,会影响到实参的值。因为引用类型的对象是存储在堆内存中的,形参和实参都是指向同一个地址,地址内的数据改变了形参和实参也就变了
  • 注意:如果形参的地址改变了,那么这个改变不会影响到实参。
  • 注意:String类型比较特殊,它是存储在常量池中的,你可以把它近似看成基本数据类型的。因为它的值一旦确定,就无法改变。

在这里插入图片描述

总结
  • java的基本数据类型是传值调用,引用类型是传引用调用,即java参数之间传递的是变量存储的内容,基本类型存储的是值本身,引用类型存储的内容是对象的地址;
  • 当传值调用时,改变的是形参的值,并没有改变实参的值,实参的值可以传递给形参,但是,这个传递是单向的,形参不能传递回实参;
  • 当使用引用数据类型作为方法的形参时,若在方法体中修改形参指向的数据内容,则会对实参变量的数值产生影响,因为形参变量和实参变量共享同一块堆区;
  • 当使用引用数据类型作为方法的形参时,若在方法体中修改形参变量的指向(地址),此时不会对实参变量的数值产生影响,因为形参变量和实参变量分别指向不同的堆区;
  • 当传引用调用时,如果参数是新对象,无论对新对象做了何种操作,都不会改变实参对象,因为两个对象的地址不同,但是如果改变了对象的内容,就会对应改变实参对象的内容。

如:

public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
private String name;
public A(String name) {
    this.name = name;
}
public void test(A a){
    a = new A("李四");
    a.setName("李四");
}
public static void main(String[] args) {
    A a = new A("张三");
    System.out.println("方法调用前:" + a.getName());
    a.test(a);
    System.out.println("方法调用后:" + a.getName());
    a.test(new A("王五"));
    System.out.println("传入新对象后:" + a.getName());
    a.test(new A("马六"){
        @Override
        public void test(A a) {
            super.test(a);
            System.out.println("匿名内部类的name:" + a.getName());
        }
    });
    System.out.println("传入匿名内部类后:" + a.getName());
}
//    结果:
//    方法调用前:张三
//    方法调用后:张三
//    传入新对象后:张三
//    传入匿名内部类后:张三
  • 当传入一个对象或数组时,形参与实参共享一个对象地址,形参修改该对象的属性会影响到实参,因为实参和形参都指向该对象,而该对象的属性被改变,并不会改变该对象的地址
public class TestThread1{
    public static void main(String[] args) {
        /**
         * 输出:
         * 方法调用前:10
         * 方法调用后:100
         */
        F f = new F();
        System.out.println("方法调用前:" + f.value );
        show(f);
        System.out.println("方法调用后:" + f.value );
    }
     static void show(F f){//改变对象的属性
        f.value = 100;
    }
}
class F{
    int value = 10;
}

值传递机制:

  • 基本数据类型:传递值的副本,即复印件,副本改变不会影响原件
  • 引用数据类型:传递的也是值的副本,但是引用类型的值指的是对象的地址,原件和复印件都指向同一个地址,所以只要该地址的内容改变了,所有指向该地址的引用都会改变

虚方法

与声明的类无关,由实际创建的对象决定调用哪个类的方法,如:person p=new student(),虽然声明的是person类,但是他实际创建的却是Student对象,所以调用student类的方法。又因为在编译过程是虚的,要在运行过程再去决定调用哪个方法,所以称为虚方法。程序中大部分方法都是虚方法。

三种非虚方法:

​ 1)static方法以声明的类型为准,与实例无关。​
​ ‌static void s(){};
​ A a=new B();
​ a.s();// 无论是创建哪个对象,调用的都是同一个是s()
​ 声明的是A类,所以调用的也是A类,与实例new B()无关。
​ 2)private方法:因为子类无法调用,所以也无法虚化。
​ 3)final方法因为无法被覆盖,所以也不能虚化。

方法的注意事项

  • 概念:方法执行后的返回结果。
  • 不调用则不执行,即方法创建后不会立刻使用,而是等调用者调用才执行(main方法是程序的入口,被JVM自动调用)
  • 方法必须先定义,后调用,否则程序将报错
  • 方法与方法是平级关系,不能嵌套定义
  • 方法定义的时候,参数之间用逗号隔开
  • 如果方法有明确的返回值,一定要有return语句,以便返回一个值
  • void空返回类型,return可以省略或return后面不能跟数据
  • return语句必须放在方法最后一行,除非是放在判断语句块内

代码块

概述:在Java中,使用{}括起来的代码被称为代码块。

分类

分类(根据其位置和声明的不同):

(1)局部代码块
    位置:在方法中的局部位置出现
    作用:用于限定变量的生命周期(作用域),及早释放空间,提高内存利用率

(2)构造代码块(属于对象成员,存储在堆内存中)
    位置:在类中方法外出现
    作用:把多个构造方法中的相同代码存放到一起,用于给对象进行初始化
    执行特点:每次调用构造方法时都执行,而且在构造方法前执行,每创建一个对象,执行一次代码块

(3)静态代码块(属于类成员,存储在方法区里面的静态区中)
    位置:在类中方法外出现,并且在前面加了static修饰符
    作用:用于给类进行初始化
    执行特点:加载类时执行,并且只执行一次,因此它在构造代码块和构造方法前执行

(4)同步代码块(多线程内容)

(五)方法重载

概述:在同一个类中多个具有相同方法名参数类型或数量、参数顺序不同的方法相互之间构成重载

  • 只能在当前类中使用重载的方法
  • 方法重载与返回值无关,只与方法名、参数列表有关
  • 调用重载方法时,首先通过方法名定位,有相同方法名时,优先调用与参数类型一致的方法,当没有与传递参数类型一致的方法时,再去找可以进行转化的方法
  • 在调用时,JVM通过参数列表的不同来区分同名方法
  • 好处:灵活、方便、屏蔽使用差异。
  • 弊端:每一个方法与某一种具体类型形成了密不可分的关系,耦合太高

注意:

  • 重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式
  • 重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载
方法重载练习

需求:使用方法重载的思想,设计比较两个整数是否相同的方法,兼容全整数类型(byte,short,int,long)
思路:
①定义比较两个数字的是否相同的方法compare()方法,参数选择两个int型参数
②定义对应的重载方法,变更对应的参数类型,参数变更为两个long型参数
③定义所有的重载方法,两个byte类型与两个short类型参数
④完成方法的调用,测试运行结果

public static void main(String[] args) {
    //调用方法
    System.out.println(compare(10, 20));
    System.out.println(compare((byte) 10, (byte) 20));
    System.out.println(compare((short) 10, (short) 20));
    System.out.println(compare(10L, 20L));
}
//int
public static boolean compare(int a, int b) {
    System.out.println("int");
    return a == b;
}
//byte
public static boolean compare(byte a, byte b) {
    System.out.println("byte");
    return a == b;
}
//short
public static boolean compare(short a, short b) {
    System.out.println("short");
    return a == b;
}
//long
public static boolean compare(long a, long b) {
    System.out.println("long");
    return a == b;
}


Java遵循就近原则,会自动寻找最符合的类型
public static void print(Object obj){
    System.out.println("Object");
}
public static void print(String str){
    System.out.println("String");
}

public static void main(String[] args) {
    print(null);//输出String
}

(六)构造方法

构造方法:类中的特殊方法,主要用于初始化对象,给对象的成员变量赋初始值。

public class 类名{
    修饰符 类名( 参数 ) {
    }
}

特点:

  • 名称与类名完全相同。
  • 没有返回值类型,也不能写void。
  • 创建对象时,触发构造方法的调用,不可通过.手动调用。

注意:如果没有在类中显示定义构造方法,则编译器默认提供无参构造方法。如果手动定义了构造方法,则系统不再提供默认构造方法,可根据需求自行添加。

在这里插入图片描述

  • 构造器(构造方法)是用来初始化对象化的,并不是用来创建对象的
  • 构造方法不能被继承,因为构造方法的名字必须与类名一致,但是可以被调用
构造方法重载

构造方法也可重载,遵循重载规则

创建对象时,根据传入参数,匹配对应的构造方法

在这里插入图片描述

构造方法为属性赋值
  • 创建对象的同时,将值传入构造方法
  • 由构造方法为各个属性赋值

在这里插入图片描述

(七)this关键字

  • 类是模板,可服务于此类的所有对象;
  • this是类中的默认引用,代表当前实例;
  • 当类服务于某个对象时,this则指向这个对象;
  • this不能在类定义的外部使用,只能在类定义的方法中使用。

this的本质就是已创建对象的地址,代表当前对象

记住 :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁

在这里插入图片描述

this的三种用法:

  1. 访问字段和方法。
this.成员变量名  调用本类的成员变量
this.成员方法名();  调用本类的成员方法
  1. 区分字段和局部变量。
public class Test{
  private String name = "张三";
  public static void main(String[] args){
      String name = "李四";
      System.out.println(name); // 输出李四
      System.out.println(this.name); // 输出张三
  }
}
  1. 在构造方法中调用本类的其他构造方法(必须写在构造方法的第一句,且只能调用一个,一般用参数少的构造方法调用参数多的方法,没有的参数直接给默认值,这样方便代码阅读),注意:只能在构造方法中使用,且只能写在第一行
  • this():调用无参构造
  • this(实参):调用有参构造
  • 目的:减少重复代码
class Teacher{
	String name;
	int age;
    double salary;
    public Teacher() {
		System.out.println("无参构造方法执行完毕");
	}	
    /*
	public Teacher(String name) {
        this.name = name; // 重复写赋值语句,代码冗余且麻烦
	}
	public Teacher(String name , int age) {
	// 重复写赋值语句,代码冗余且麻烦
		this.name = name;
		this.age = age;
	}
	*/
    public Teacher(String name) {
        // 默认this();即默认调用本类无参构造器
        //this.name = name;
		this(name,0,0.0);//调用带三个参数的构造方法
	}
	public Teacher(String name , int age) {
		//this.name = name;
		//this.age = age;
        this(name,age,0.0);//调用带三个参数的构造方法
	}
    public Teacher(String name , int age, double salary) {
		this.name = name;
		this.age = age;
        this.salary = salary;
	}

}

(八)super关键字

  • super关键字代表父类存储空间的标识(可以理解为父类引用,借此操作父类的成员)。
  • 用法跟this关键字差不多,表示调用父类的成员。访问时将忽略本类,直接从父类开始查询,即就算子类重写了该成员,也不会被使用,只会使用父类的成员

可以看成直接父类对象的引用,用来访问被子类覆盖的直接父类的方法和属性

在这里插入图片描述

super的三种用法:

1、访问父类的方法和字段。

super.成员变量名  调用父类的成员变量
super.成员方法名();  调用父类的成员方法

2、区分子类与父类中同名的字段和方法。

class Son extends Father{
// 父子类的同名属性不存在重写关系,两块空间同时存在(子类遮蔽父类属性),需使用不同前缀进行访问。
	int field = 20;
	public void run(){
		System.out.println("子类重写了父类的跑步方法");
		super.run(); // 访问父类的run()方法
	}
	public void method() {
		System.out.println(super.field);//在子类中访问父类的field属性
		System.out.println(this.field); // 访问本类的field属性
	}
	public static void main(String[] args) {
		Son son = new Son();
		son.method();
		son.run(); // 访问本类的run()方法
	}
}

3、在构造方法中调用父类的构造方法(必须写在构造方法的第一句)。

super():表示调用父类无参构造方法。

super(实参):表示调用父类有参构造方法。

class Father{
    int field;//父类提供的属性
    public Father() {
        System.out.println("Father() - 无参构造被执行");
    }
    public Father(int a) {
        this.field = a;
        System.out.println("Father(int a) - 有参构造被执行");
    }

}
class Son extends Father{
    public Son() {
        //super(); 没显式声明调用时,默认调用父类无参构造
        System.out.println("Son() - 无参构造被执行");
    }
    public Son(int b) {
        super(b);
        System.out.println("Son(int b) - 有参构造被执行");
    }
    public Son(double c) {
        super(1);
        System.out.println("Son(double c) - 有参构造被执行");
    }
    public static void main(String[] args) {
        new Son();
        System.out.println("-------------");
        Son son = new Son(10);
        System.out.println("-------------");
        new Son(3.5);
        System.out.println(son.field);
    }
}

(九)this和super

  • this:调用本类的属性和方法,包括继承来的。从本类开始查找属性和方法,没有则查找父类
  • this():调用本类的构造方法
  • super:调用父类的属性和方法。忽略本类,直接从父类开始查找属性和方法
  • super():调用父类的构造方法。
  • this()和super()都不能在static环境中使用,包括static变量,static方法,static语句块。
  • this()和super()不能在同一个构造方法中使用,this()和super()有且只能有一个,因为他们都需要放在构造方法的第一行,如果this()和super()都不写,则默认调用父类的无参构造方法,因为在子类的构造方法中会默认添加一个super(),前提是父类必须拥有无参构造方法,方法里面可以什么都没有。

注意:当父类无参和有参构造方法都具备的时候,默认调用无参构造方法,即当没有表明时,在子类构造方法的第一句默认添加super(),也可以显式的声明调用有参构造方法super(参数列表),子类构造方法可以随意调用父类的有参、无参构造方法;但是,当父类只有有参构造方法而没有无参构造方法时,必须在子类构造方法的第一句显式的声明super(参数列表)

this和super都指的是同一个对象

this和super都指的是同一个对象,只是在内存中的查找顺序不同,this是从本类开始查找,super是从父类开始查找

public class Test extends Car {
    public static void main(String[] args) {
        new Test().p();
    }
    public void p(){
        System.out.println(this.getClass());  // 输出 class Test
        System.out.println(super.getClass());  // 输出 class Test
    }
}
class Car {}

面向对象-三大特性

面向对象的三大特性是封装、继承、多态

访问修饰符

修饰符作用
private私有的,只能在本类中调用
default默认的(可以省略不写),只能在同一个包中调用
protected允许在同一个包中或其他包中的子类调用
public开放的,在哪都可以调用
作用范围
作用域本类同包不同包下的子类全部地方
private
default
protected
public

注意:父子类都在一个包中,子类可以访问父类的protected成员,在子类中创建一个父类对象时,父类对象也可以访问protected成员

父子类不在同一个包中,子类可以访问父类的protected成员,在子类中创建一个父类对象时,父类对象不可以访问protected成员

一、封装

为什么要封装?

  • 直接在对象的外部为对象的属性赋值,并没有对属性的赋值加以控制,就可能存在非法数据的录入。
  • 封装作用:
    1)减少耦合
    2)类内部的结构可以自由修改
    3)对其成员进行更精确的控制
    4)隐藏信息,实现细节
一)什么是封装?

概述:隐藏对象的属性和内部实现细节,仅对外提供公共访问方法,控制对象的修改及访问的权限。

二)封装的原则
  • 将不需要对外提供的内容都隐藏起来
  • 属性一般用private修饰

私有属性由get/set方法进行读取和赋值操作,boolean类型的get方法改为is开头

  • 要访问被封装的私有属性,必须通过set、get方法
  • 除了只在本类使用的方法用private修饰外,其他方法都使用public修饰
三)封装的实现步骤
(1)用private关键字把属性设置为私有
(2)提供公共的setter和gette方法,用public修饰
(3)通过setter方法对输入的数据进行过滤
小提示:调用get、set方法时,get方法可以直接放在输出语句中,set不可以

在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全。

public class TestEncapsulation {
	public static void main(String[] args) {
		Student s1 = new Student();
		s1.name = "tom";
		//s1.age = 20;//安全,无法直接访问到私有属性
		s1.setAge(20);//调用方法完成赋值操作
		s1.sex = "male";
		s1.score = 99.0;
		System.out.println( s1.getAge() );//调用方法完成取值操作
	}
}
class Student{
	String name; //属性也称为字段
	private int age;//私有属性,本类可见,其他位置不可见
	String sex;
	double score;
	public Student() {}
	/**
	 * 为age属性赋值的方法
	 */
	public void setAge(int age) {
		if(age >= 0 && age <= 153) {//合法区间
			this.age = age;
		}else {
			this.age = 18;//如果录入的数据不合法,默认使用18代表用户的年龄
		}
	}
	/**
	 * 为age属性取值的方法
	 */
	public int getAge() {
		return this.age;
	}
}
如何防止构造器破坏封装

当我们定义了有参构造器时,别人可以直接通过构造器进行赋值,从而绕过set方法的校验

public class TestEncapsulation {
	public static void main(String[] args) {
		Student s1 = new Student();
		//s1.name = "tom";//,无法直接访问到私有属性
		System.out.println( s1.getAge() );//输出18
		// 但是可以通过构造器直接为属性赋值,此时将绕过set方法的校验
		Student s2 = new Student("asdkjsahdkasjdkasj",10000);
		System.out.println( s2.getName() + s2.getAge() );//输出 asdkjsahdkasjdkasj10000
	}
}
class Student{
	private String name;
	private int age;
	public Student() {}
	public Student(String name,int age) {
		this.name = name;
		this.age = age;
	}
	/**
	 * 为name属性赋值的方法
	 */
	public void setName(String name) {
		if(name.length() >= 2 && name.length() <= 10) {//名字长度在2-10个字符之间
			this.name = name;
		}else {
			this.name = "java";
		}
	}
	/**
	 * 为name属性取值的方法
	 */
	public int getName() {
		return this.name;
	}
	/**
	 * 为age属性赋值的方法
	 */
	public void setAge(int age) {
		if(age >= 0 && age <= 150) {//合法区间
			this.age = age;
		}else {
			this.age = 18;//如果录入的数据不合法,默认使用18代表用户的年龄
		}
	}
	/**
	 * 为age属性取值的方法
	 */
	public int getAge() {
		return this.age;
	}
}
针对声明了有参构造器的类,我们可以通过以下方式进行封装:

在有参构造器中调用set方法,不直接使用属性进行赋值,防止其绕过set方法的校验

public class TestEncapsulation {
	public static void main(String[] args) {
		Student s1 = new Student();
		//s1.name = "tom";//,无法直接访问到私有属性
		System.out.println( s1.getAge() );//输出18
		// 因为有参构造器内部调用set方法为属性赋值,所以要遵守set方法的校验规则
		Student s2 = new Student("asdkjsahdkasjdkasj",10000); // 该数据不合理,进行默认赋值
		System.out.println( s2.getName() + s2.getAge() );//输出java18
	}
}
class Student{
	private String name;
	private int age;
	public Student() {}
	public Student(String name,int age) {
	// 在构造器中调用set方法
		setName(name);
		setAge(age);
	}
	/**
	 * 为name属性赋值的方法
	 */
	public void setName(String name) {
		if(name.length() >= 2 && name.length() <= 10) {//名字长度在2-10个字符之间
			this.name = name;
		}else {
			this.name = "java";
		}
	}
	/**
	 * 为name属性取值的方法
	 */
	public int getName() {
		return this.name;
	}
	/**
	 * 为age属性赋值的方法
	 */
	public void setAge(int age) {
		if(age >= 0 && age <= 150) {//合法区间
			this.age = age;
		}else {
			this.age = 18;//如果录入的数据不合法,默认使用18代表用户的年龄
		}
	}
	/**
	 * 为age属性取值的方法
	 */
	public int getAge() {
		return this.age;
	}
}

二、继承

概述:多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

定义:子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

  • 两个类之间的继承关系,必须满足“is a”的关系。

可以使用IDEA中的类图来查看类之间的关系

IDEA类图的字段与属性的区别

  • 字段:就是平时所说的成员变量(属性)
  • 属性:特指以set或get开头的方法,提取set或get后面的名称当做属性

在这里插入图片描述

在这里插入图片描述

一)继承的作用

继承设计思想:

  • 父类构造器完成父类属性的初始化

  • 子类构造器完成子类属性的初始化
    好处:

  • 强代码的复用性,更容易实现类的扩展(多个类相同的成员可以放到同一个类中)

  • 提高了代码的可维护性(如果某功能的代码需要修改,只需修改一处即可),方便对事务建模

  • 让类与类之间产生了关系,是多态的前提,这里其实也是继承的一个弊端:类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性

开发的原则:低耦合,高内聚
  • 耦合:类与类的关系
  • 内聚:自己完成某件事情的能力
二)父类的选择

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,这个类就是父类,基类或者超类

  • 功能越精细,重合点越多,越接近直接父类。
  • 功能越粗略,重合点越少,越接近Object类。(万物皆对象的概念)

在这里插入图片描述

public class TestExtends {
	public static void main(String[] args) {
		Dog dog1 = new Dog();
		dog1.breed = "哈士奇"; //继承自父类
		dog1.age = 2;  //继承自父类
		dog1.sex = "公";  //继承自父类
		dog1.furColor = "灰白"; //子类独有的
		
		dog1.eat();  //继承自父类
		dog1.sleep();  //继承自父类
		dog1.run(); //子类独有的
		
		//------------------------------------------
		
		Bird bird1 = new Bird();
		bird1.breed = "麻雀";
		bird1.age = 1;
		bird1.sex = "雄";
		bird1.furColor = "棕";
		
		bird1.eat();
		bird1.sleep();
		bird1.fly();
	}
}
//父类
class Animal{
	String breed;
	int age;
	String sex;
	
	public void eat() {}
	public void sleep() {}
}

class Dog extends Animal{ //完成继承关系
	String furColor;

	public void run() {}
}

class Bird extends Animal{
	String furColor;

	public void fly() {}
}

class Fish extends Animal{
	public void swim() {}
}

class Snake extends Animal{
	public void climb() {}
}
三)声明格式
// Java通过extends关键字可以实现类与类之间的继承
// 产生继承关系之后,子类可以使用父类中的属性和方法,也可定义子类独有的属性和方法
class 子类名 extends 父类名{}
四)继承的特点

在这里插入图片描述

  • java是单继承,一个子类只能有一个直接父类,但是可以多层级间接继承其他父类的属性和方法,一个父类可以有多个子类(使用接口可以实现多继承)
  • 子类只能继承父类所有非私有成员(成员方法和成员变量),但是可以通过非私有成员访问父类的私有成员,这里其实也体现了继承的另一个弊端:打破了封装性。所以,不要为了部分功能而去继承
  • 子类不能继承父类的构造方法(构造方法名必须与类名一致,继承之后子类名与父类构造方法名称不一致),但是可以通过super关键字去访问父类的构造方法
  • Java中所有类的父类都是Object类,即如果不使用extends标明继承的直接父类,则系统默认该类的直接父类是java.lang.Object类
  • 有些语言是支持多继承的,格式:class 子类名 extends 父类名1,父类名2…{}
五)方法重写(override)

思考:为什么需要在子类中定义和父类相同的方法?

当父类提供的方法无法满足子类需求时,可在子类中定义和父类相同的方法进行重写(Override)。

概述:子类中出现了和父类中一模一样的方法声明的动作称为方法重写,也被称为方法覆盖,方法复写。重写是实现多态的必要条件,子类通过重写父类方法,用自身行为替换父类行为。

方法重写原则(两同两小一大):

1)两同 :子类方法名和形参列表必须与父类的一致

2)两小:子类方法的返回值类型和声明异常类型要小于等于父类方法(子类方法的 返回值类型和声明异常类型 必须是父类方法的 返回值类型和声明异常类型 或者 是其返回值类型和声明异常类型的子类型

3)一大:子类方法的访问权限的范围要大于或等于父类方法

方法重写与重载的区别
名称发生范围方法名形参列表返回值类型异常声明类型修饰符
重载(overload)本类必须一样类型、个数或顺序至少有一个不同无要求无要求无要求
重写(override)父子类必须一样必须一样子类方法的返回值类型必须和父类方法的返回值类型一致,或者是父类方法返回值类型的子类型子类方法的声明异常类型必须和父类方法的声明异常类型一致,或者是父类方法声明异常类型的子类型子类方法的访问权限的范围要大于或等于父类方法
六)继承中的成员关系
查找顺序(就近原则):
  • 子类成员和父类成员名称不同时,根据执行顺序查找

  • 子类成员和父类成员名称相同时:

/**
 * 首先在子类方法的局部范围查找
 * 然后在子类成员范围查找
 * 接着在父类成员范围查找(不能访问父类的局部范围)
 * ...
 * 最后还是没有找到,就报错
 * 若是找到了,但是不符合访问规则,如直接访问父类私有成员等,也会报错
 * 示例:
 */
class Fu {
    // Fu中的成员变量。
    int num = 5;
}
class Zi extends Fu {
    // Zi中的成员变量
    int num = 6;
    public void show() {
        // 访问父类中的num,但是由于子类属性重名,导致父类属性值被覆盖,若使用super.num则会直接输出父类属性的值
        System.out.println("Fu num=" + num);
        // 访问子类中的num
        System.out.println("Zi num=" + num);
    }
    public static void main(String[] args) {
        // 创建子类对象
        Zi z = new Zi();
        // 调用子类中的show方法
        z.show();
    }
}
演示结果:
Fu num = 6
Zi num = 6

为什么名称相同时先查找子类成员?

因为java中首先执行的是父类的成员,其次才是子类的成员,又由于子类成员与父类成员的名称相同,在子类进行访问时,父类成员的值会被子类所覆盖(使用super直接访问除外),所以先查找父类成员没有意义,只有在子类中没有查到该成员时,才会去父类查找

使用super访问父类成员时,要遵循封装原则,不能直接访问父类私有成员,可以借助父类暴露出来的访问接口来进行间接访问,如通过getXxx方法和setXxx方法等
七) 继承中的构造方法

父子类的初始化(分层初始化):

先进行父类的初始化,再进行子类的初始化。即加载类信息时先加载父类的信息,再加载子类的信息

子类中所有的构造方法默认都会访问父类中的无参构造方法

子类中每一个构造方法的第一条语句默认都是:super()

子类会继承父类中的属性,所以,子类在初始化之前,一定要先完成父类属性的初始化(没有父类成员,子类何谈继承)。

子类通过super(…)显式调用父类的带参构造方法时,将不再访问父类的无参构造方法

注意:子类不能多次调用父类的构造方法

如果父类中没有无参构造方法,程序在编译时会报错。如何解决呢?

1)在父类中添加一个无参构造方法
2)子类通过super(参数)去显式调用父类其它的带参构造方法
3)子类通过this(参数)去调用本类的其它构造方法,而本类的其它构造方法也必须首先访问了父类的构造方法
注意:

  • 构造方法之间的调用只能调用一次
  • super(…)或者this(…)必须出现在第一条语句上,否则,就会对父类的数据进行多次初始化。
  • super(…)和this(…)不能同时出现在子类的同一个构造方法中。
八)instanceof关键字

概述:instanceof是一个关系运算符,用来判断一个对象的运行类型是不是该类或其子类(只能用于判断具有继承关系的类,两个无关的类不能使用instanceof判断

格式:

对象名 instanceof 类名

例:  student   instanceof   Person    
类型判断:判断 student 对象的运行类型是否是Person类型或Person的子类型,如果两个对比的对象不存在直接和间接的继承关系,那么将不能编译通过;如果student 不是Person类型对象或Person的子类型对象,将返回false

三、多态

概述:某一个对象(事物),在不同时刻表现出来的不同状态(或者说:同一个方法由于调用对象的差异导致不同的行为),注意:多态是方法的多态,而不是属性的多态,即多态与属性无关

如学生对象在不同时刻的身份:

在这里插入图片描述

1、多态的前提:

1)存在继承关系

2)父类方法被子类方法重写

3)父类引用指向子类对象: 父类 引用名 = new 子类();

2、多态的利弊
好处:
  • 一致的类型:所有子类对象都可以当作共同(共有)的父类对象来处理
    多态的优势主要提现在向上转型

  • 提高了程序的可扩展性(由多态保证)

    • 使用父类作为方法形参实现多态,使方法参数的类型更为宽泛
    • 使用父类作为方法返回值实现多态,使方法可以返回不同子类对象
  • 提高了程序的可维护性(由继承保证)

弊端:

不能访问子类特有的方法。因为编译的时候主要看左边的类型,左边是父类型,所以不能访问子类型特有的方法。

3、多态的转型

前面提到了,多态有一个弊端,那就是不能访问子类特有的方法,这里可以利用向下转型解决这个问题

向上转型

概述:声明父类引用指向子类对象,属于自动类型转换,父类 引用名 = new 子类();

注意:向上转型只能调用父类拥有的属性和方法。在调用成员方法和成员变量进行编译时,编译器默认其为声明的类型,即声明什么类型的对象引用,则编译器就认为该对象是此类型。

如: Animal a = new cat(); // 编译器默认new 出来的是Animal类型,也只能调用Animal类的成员

但是:编译完成后,在实际执行中,调用成员变量时,还是调用Animal类的成员变量(因为多态与属性无关,所以无论是编译还是运行阶段都只看声明的类型,即等号左边的类型),调用成员方法时,默认会调用Cat类中重写Animal类的方法,即调用成员方法时,由于父类方法被子类同名方法所覆盖,所以调用的是子类的同名方法;若Cat类没有重写该方法,则在Animal类中查找,其遵循继承的查找规则。编译看左边,执行看右边,等号左边是编译类型,右边是运行类型

多态中的成员访问特点:

(1)成员变量

编译看左边,执行也看左边。

成员变量的访问是就近原则。声明的是父类的引用,所以,访问的是父类的成员变量。

(2)构造方法

创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。

(3)成员方法

编译看左边,执行看右边

  • 这里存在java的动态绑定机制:当调用对象方法的时候,该方法回合该对象的内存地址/运行类型绑定。所以非静态的成员方法始终跟着对象走。创建的是子类对象,则调用的也是子类的成员方法。
public class Test{
	public static void main(String[] args){
		Person p = new Son();
		/* 动态绑定机制:虽然声明的是Person类,但是实际上指向的却是Son对象。
		   由于Son类重写了sum方法,所以调用的是Son对象的sum方法,又由于属性没有多态,
		   只遵循就近原则,哪里声明就在哪里调用,所以输出150
		 */
		System.out.println(p.sum());
		/* 动态绑定机制:虽然声明的是Person类,但是实际上指向的却是Son对象。
		   由于Son类重写了sum1方法,所以调用的是Son对象的sum1方法,又由于属性没有多态,
		   只遵循就近原则,哪里声明就在哪里调用,所以输出120
		 */
		System.out.println(p.sum1());
		// 声明的是Person类,所以调用的是Person类的 i属性,输出10
		System.out.println(p.i); 
	}
}
class Person{
	public int i = 10;
	public int sum(){
		return i + 50;
	}
	public int sum1(){
		return getI() + 50;
	}
	public int getI(){
		return i;
	}
}
class Son{
	public int i = 100;
	public int sum(){
		return i + 50;
	}
	public int sum1(){
		return i + 20;
	}
	public int getI(){
		return i;
	}
}

当子类只重写了个别方法时:

public class Test{
	public static void main(String[] args){
		Person p = new Son();
		/* 动态绑定机制:虽然声明的是Person类,但是实际上指向的却是Son对象。
		   由于Son类没有重写sum方法,所以调用的是Person类的sum方法,又由于
		   属性没有多态,只遵循就近原则,哪里声明就在哪里调用,所以输出60
		*/
		System.out.println(p.sum());
		/* 动态绑定机制:虽然声明的是Person类,但是实际上指向的却是Son对象。
		   由于Son类没有重写sum1方法,所以调用的是Person类的sum1方法,又由于
		   Son类重写了getI方法,且实际指向的是Son对象,所以调用Son对象的
		   getI方法,getI方法返回i属性,又由于属性没有多态,只遵循就近原则,
		   哪里声明就在哪里调用,输出150
		*/
		System.out.println(p.sum1());
		// 声明的是Person类,所以调用的是Person类的 i属性,输出10
		System.out.println(p.i); 
	}
}
class Person{
	public int i = 10;
	public int sum(){
		return i + 50;
	}
	public int sum1(){
		return getI() + 50;
	}
	public int getI(){
		return i;
	}
}
class Son{
	public int i = 100;
	public int getI(){
		return i;
	}
}

(4)静态成员(静态变量和静态方法)

编译看左边,执行也看左边。

静态成员与类相关。声明的是父类的引用,所以,访问的是父类的静态成员。

结论:由于成员方法存在方法重写,所以它在执行的时候看右边。

向下转型

声明子类引用指向父类,属于强制类型转换(可调用子类的方法和属性

要求父类引用必须能够转换为子类引用,否则将会报:ClassCastException:类型转换异常

子类 引用名 = (子类)父类名;

向下转型使用场景
  • 要对子类做特殊处理时才使用向下转型
  • 向下转型后才可以调用子类特有的成员
  • 注意:如果进行向下转型的对象本来就是父类型,那么转换为子类型时编译不会报错,但是运行时会报错

如:

class Father{}
class Daughter extends Father{}
class Son extends Father{
    public static void main(String[] args){
        Father fa = new Son(); // 编译通过
        Son son = (Son)fa; // 运行不会报错,因为fa指向的本来就是Son类型对象
        
        Father f = new Father();//本来就是父类型
		//进行向下转型后,编译不报错,运行报错,因为运行时会找到实际创建的对象,而这里实际创建的是Father类型,强行转换成Son类型就会报错
        Son s = (Son) f ;
        
         Father father = new Son();
		//进行向下转型后,编译不报错,运行报错,因为运行时会找到实际创建的对象,而这里实际创建的是Son类型,与Daughter无关,强行转换成Daughter类型就会报错
        Daughter d = (Daughter) father ;
    }
}

编译器只能识别声明的类型,声明父类编译器就认为该类型为父类型,只能调用父类拥有的子类方法,但是本质上还是子类的对象;声明父类引用指向子类对象之后,也可以将该引用强制转换成另一个子类的引用,编译器不会报错,但是因为本质上是第一个子类的对象,两个子类并不相同,所以运行时会报类型转换错误

四、static关键字

概述:static关键字可以用来修饰成员变量和成员方法,表示静态的

  • 静态就是创建一个程序共有的成员,只要静态对象修改了,所有调用该静态的引用都会随之改变;它是类的,不属于任何一个实例对象,它不是存储在任何一个对象的空间,而是存储在类的空间,它可以通过类名或对象名调用;对象的方法在内存中有专用的代码段,而类方法却没有

  • 静态修饰的内容我们一般称其为:与类相关的,即类成员;而非静态的,称其为:对象成员

  • 被static修饰的成员存储在方法区中的静态区

    方法区的分区:类文件区、静态区、方法代码区、常量池

在这里插入图片描述

一)特点
  • 随着类的加载而加载

    类加载时机:
    (1)创建对象(包括创建子类对象,即任意对象)。
    (2)访问静态属性/静态方法。
    (3)主动加载:Class.forName(“全限定名”);
  • 优先于对象存在。所以,可以被所有对象共享。

  • 被类的所有对象共享,在全类中只有一份,不因创建多个对象而产生多份。(这也是我们判断是否使用静态关键字的条件)

  • 可以通过类名直接调用(其实它本身也可以通过对象名调用,推荐使用类名调用)

    通过类名调用:
    类名.静态成员变量名;      类名.静态成员方法名();
    
    通过对象名调用(不推荐):
    new 类名().静态成员变量名;  new 类名().静态成员方法名();
    

二)静态分类
1、静态代码块

静态语句块用来初始化类,构造方法用来初始化对象

定义在成员位置,随着类的加载而执行且最多只执行一次,优先于main方法和构造方法的执行。

格式:

static { 内容 };
2、静态属性

当 static 修饰成员变量时,该变量称为类变量(类属性,静态变量)

该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。

格式:static 数据类型 变量名;

3、静态方法

当 static 修饰成员方法时,该方法称为类方法(静态方法) 。静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单.

修饰符 static 返回值类型 方法名 (参数列表){
	// 执行语句
}
三)static注意事项
  • 静态方法只能调用静态变量、其他静态方法,不能使用this和super关键字,因为这两个关键字代表的是当前对象和父类对象,而静态只属于类,当类存在的时候,静态也存在,对象不一定存在,所以不能有这两个关键字或者其他属于对象的变量和方法

  • 静态成员只能访问静态成员,非静态成员可以访问静态和非静态的成员

  • 静态方法与非静态方法的区别:在编译期间静态方法的行为就已经确定,而实例方法只有在运行期间当实例确定之后才能确定,也就是说静态方法一开始的行为就确定了,而实例方法要等创建对象才能知道。

  • 静态语句块、属性、方法只在类加载时执行且仅执行一次,就是说,无论创建了几个对象,静态代码块、属性、方法永远都是只执行一次。

  • 什么时候定义静态成员?

    • (1)什么时候定义静态属性

      如果是每个对象都需要使用到的属性,就定义为静态属性

    • (2)什么时候定义静态方法

      如果这个方法中没有调用非静态的数据,就可以定义为静态方法。静态方法一般用于一些工具类的开发。

四)静态变量和成员变量的区别
1)所属不同

    静态变量属于类,所以也称为类变量

    成员变量属于对象,所以也称为实例变量(对象变量)

(2)存储在内存中位置不同

    静态变量存储于方法区的静态区

    成员变量存储于堆内存

(3)生命周期

    静态变量随着类的加载而加载,随着类的消失而消失

    成员变量随着对象的创建而存在,随着对象的消失而消失

(4)调用不同

    静态变量可以通过类名调用,也可以通过对象名调用

    成员变量只能通过对象名调用

五、java中个语句的执行顺序

1)父类静态属性和静态代码块初始化(优先级相同):到底是静态属性先执行还是静态代码块先执行,要看其声明的顺序,声明顺序在先的先执行

2)子类静态属性和静态代码块初始化:先声明先执行

3)main方法

4)父类代码块

5)父类构造方法

6)父类普通方法

7)子类代码块

8)子类构造方法

9)普通方法

普通代码块与普通属性初始化的优先级相同

注意:父类的成员一定是先于子类成员执行的,如:父类静态属性和静态代码块一定是最先执行的,然后才到子类的静态属性和静态代码块,再到main方法,父类构造器/代码块/普通静态方法/实例方法、子类构造器/代码块/普通静态方法/实例方法,这几个的执行顺序主要是看在main方法中被调用的顺序,一般先被调用的先执行,但是,在父类构造器/代码块和子类构造器/代码块这四者之间的执行顺序是固定的,必定先执行父类普通代码块,再执行父类构造器,然后到子类普通代码块,然后才是子类构成器

构造方法与静态语句块的执行顺序

1)执行静态语句块(因为要先把类信息加载出来,才能构建对象,先有设计图纸,才有具体物品),向上追溯到Object类的静态语句块,一层一层执行下来,直至到本语句块,即如果继承了Object类,则必须先执行Object类的静态语句块,然后才执行本语句块

2)执行构造方法(执行顺序与静态语句块一致),因为构造方法第一句默认super(),会向上追溯到Object类

六、final关键字

概述:final关键字是一个修饰符,表示最终的,它可以用来修饰类、方法以及变量。

final特点

1)final修饰的变量将成为常量,无初始值必须对其赋值且只能赋值一次

格式:

基本类型:
fianl 基本类型 常量名 = 字面值常量;

public static fianl 基本类型 常量名 = 字面值常量;  (此处只能是成员变量)

引用类型(对象):
fianl 引用类型 常量名 = 常量值;

public static fianl 引用类型 常量名 = 常量值;  (此处只能是成员变量)

对象引用所指向的地址值不能发生改变,但对象的属性值可以发生改变。
  • 基本类型:其值不能发生改变

  • 引用类型:其地址值不能发生改变,但是,该对象的堆内存中的数据值是可以改变的

2)final修饰的方法不可以被子类重写,但可以重载、继承

格式: 修饰符 final 返回值类型 方法名(参数列表){ //方法体 }

3)final修饰的是最终类,不可被继承,没有子类

格式: final class 类名 {}

注意:final修饰变量分以下几种情况:

1)局部常量:修饰局部变量时,可以不进行初始化操作,但必须且只能赋值一次后才能被使用
public static void main(String[] args) {
    // 声明变量,使用final修饰
    final int a;
    // 第一次赋值
    a = 10;
    // 第二次赋值
    a = 20; // 报错,不可重新赋值
    // 声明变量,直接赋值,使用final修饰
    final int b = 10;
    // 第二次赋值
    b = 20; // 报错,不可重新赋值
}

在这里插入图片描述

2)实例常量:
  • 修饰实例变量时,可以声明变量的同时对其赋值

    public class Test {
        final String USERNAME = "张三";
        private int age;
    }
    
  • 可以先声明然后在构造方法中赋值,在构造方法中进行赋值时,类中所有构造方法都应对其进行赋值

    public class Test {
      final String USERNAME ;
        private int age;
        public Test(String username, int age) {
            this.USERNAME = username;
            this.age = age;
        }
        public Test(String username) {
            this(username,0);
        }
        public Test() {
            this.USERNAME = "张三";
        }
    }
    

    在这里插入图片描述

  • 也可以在代码块中进行赋值,但是有多个代码块时只能选择其中之一进行赋值

public class Test {
    final String USERNAME ;
    {
        this.USERNAME = "张三";
    }
//    {
//        this.USERNAME = "张三"; // 
//    }
}

在这里插入图片描述

3)静态常量
  • 修饰静态变量时,可以声明变量的同时对其赋值

    public static final String USERNAME = "张三" ;

  • 可以先声明然后在静态代码块中赋值,且多个静态代码块中只能有一个对静态常量进行赋值

public class Test {
    public static final String USERNAME;
    static {
        USERNAME = "张三" ;
    }
//    static {
//        USERNAME = "张三" ;
//    }
}

在这里插入图片描述

4)对象常量:修饰引用类型变量时,引用类型变量地址不可以改变,但是该变量的内容可以改变,对象常量也是实例常量的其中一类
public static void main(String[] args) {
    // 创建 Test 对象
    final Test t = new Test();
    // 调用setName方法
    t.setName("张三"); // 可以修改
    // 创建 另一个 User对象
    t = new Test(); // 报错,指向了新的对象,地址值改变。
}
5)被final修饰的参数在本方法中不可以被赋值。
public class Test {
    public  String USERNAME;
    public static double area(final double PI,double r) {
        return r * r * PI;
    }
    public void test(final Test name) {
        System.out.println(getClass()); // class Test
        name.USERNAME = "张三";
//        name = new Test(); // 报错,指向了新的对象,地址值改变。
        System.out.println(getClass()); // class Test
    }
    public static void main(String[] args) {
        double area = area(3.14, 5);
        System.out.println(area);
        new Test().test(new Test());
    }
}

在这里插入图片描述

声明 final 方法的主要目的是防止该方法的内容被修改。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐