当前位置: 首页 > news >正文

JavaSE:类和对象2

一、封装

封装的概念

面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性

何为封装呢?简单来说 就是套壳屏蔽细节

例如手机,你看不到任何的内部实现细节,只留下一些公开的接口给你使用(充电接口等),从Java语言上来说,想要达到同样的目的,只能通过对类进行封装把细节隐藏起来,提供一些你公开可以访问的内容就可以了

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

示例:写一个学生类

class Student{public String neame;public int age;public String stuNum;public void eat(){System.out.println(this.name + " 在吃早餐 ");}public void doClass(){System.out.println(this.name + " 在上课 ");}public Student(){System.out.println("不带参数的构造方法");}public Student(String name,int age,String stuNum){this.name = name;this.age = age;this.stuNum = stuNum;System.out.println("3个参数的构造方法");}
}    public class Test{public staatic void main(String[] args){//对象的实例化Student student1 = new Student();student1.name = "zhangsan";student1.age = 10;student1.stuNum = "12345";System.out.println(syudent1.name);System.out.println(syudent1.age);System.out.println(syudent1.stuNum);}
}

如果我们将以上代码中的成员变量public修饰改用private修饰

我们就会发现编译器会报错:

原因:原本被public修饰的成员变量/方法 叫做公共成员变量/方法,现在改为被private修饰,那么原来的成员变量/方法,就叫做私有成员变量/方法,此时只能在当前类中使用,不能在当前类 外使用。

那么,我们如果在实例化对象后,想要访问这些私有成员变量,即获得访问权限,只能通过封装来访问,即对外提供接口来供对象进行访问,一般是通过类提供的公共方法(public修饰)实现访问的

公共方法的分类

公共方法分为两类:

Getter方法(获取器)

用于 获取/读取 私有成员变量的值

命名规范get + 变量名首字母大写   例如,getName()等

Setter方法(修改器)

用于 修改 私有成员变量的值(可添加验证逻辑,例如添加 if 条件等)

命名规范set + 变量名首字母大写   例如,setName(String name),setAge(int age)等

现在我们继续解决上面的问题,在Student类中写公共方法,提供接口来实现对私有成员变量的访问:

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;
}public String geuStuNum(){return stuNum;
}
public void setStuNum(String stuNum){this.stuNum = stuNum;
}

这样就可以正常运行:

创建类公共方法的快捷键

与构造方法一样,公共方法也有自己的快捷键,选定要访问的私有成员变量,就可以快速创建

首先右键点击  Generate 

然后再点击  Getter and Setter

最后选择要访问哪些私有成员变量,点击 OK 就可以立即创建

被 private 修饰的成员方法,其访问方式和私有成员变量遵循完全相同的封装原则:它们只能在类的内部被访问,而不能通过类的对象从外部直接调用。

因此,要实现对外部提供私有成员方法的功能,就是通过一个公共方法间接地调用它

示例:

访问限定符

Java中主要通过类和访问权限来实现封装类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用

Java中提供了四种访问限定符

(包和子类后面讲解)

private:我的秘密,只有我自己能用。(同类)

default:我们部门内部共享,外人别打听。(同包)

protected:我的秘密和家传手艺,可以传给子女(子类),也在家族(同包)内共享。

public:公共服务,对所有人开放。(全局)

【说明】

  • protected主要是用在继承中,继承部分详细介绍
  • default权限指:什么都不写时的默认权限
  • 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性

private访问限定符

在讲封装的时候已经介绍过,它的范围是同一包中的同一类中

关键字: private 

作用:用于隐藏类的内部实现细节(成员变量、内部方法),只允许通过类提供的公共方法(Getter/Setter)进行间接访问和修改,从而保证数据的安全性和一致性。

public访问限定符

范围:public 是限制最宽松的访问级别,作用范围在整个项目(所有包)

关键字:public

作用:被 public 修饰的类、方法、变量可以被任何其他类访问

程序的入口 main 方法必须是 public 的,否则 Java 虚拟机(JVM)无法找到并调用它。

default(缺省)-无访问权限符

范围:同包中

关键字:无  (不写任何访问修饰符就是 default)

作用:当一个类、方法或变量没有指定任何访问修饰符时,它就拥有默认的(default)访问权限,可以在同一个包内访问,包外看不到细节。

注意:一般情况下成员变量设置为private,成员方法设置为public。类不能被private、protected访问限定符修饰。

封装扩展之包

包的概念

在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件 包。有点类似于文件夹(目录)

比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。

在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一 个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可

导入包中的类

Java 中已经提供了很多现成的类供我们使用. 例如Arrays类:可以使用 java.util.Arrays导入 java.util 这个包中的 Arrays类 

例如,我们要将一个整型数组中的元素转化为字符串,可以使用Array类中的toString方法:

那么想要使用这个类,就得导入包,当你写下 Arrays类时,按回车就会在顶部出现/导入 java.util 这个包中的Arrays类供使用

一般使用 import语句导入包,而 java.util.Arrys 中,java.util 其实就是路径(目录),也就是包,Array是这个路径下的一个java文件,也就是类,这个类中就提供了许多方法可以使用

当然还有另一种写法,就是将完整的类名写出来(包的路径写出来,然后再使用Arrays类):

public class Test{public static void main(String args){int[] array = {1,2,3,4,5,6};java.util.Arrays.toString(array);}
}

但是这样太麻烦了,还是用import语句导入的方法。

在java.util包中,不止上面说的类,如果需要使用 java.util 中的其他类, 可以使用

 import java.util.* , ( *  ---- 即通配符,用于导入一个包中的所有类,就可以不用写指定要导入的类名)

import java.util.*;  //所有java.util包下的类都可以使用public class Test{public static void main(String args){int[] array = {1,2,3,4,5,6};System.out.println(Arrays.toString(array));//使用Date类中的getTime方法得到一个毫秒级别的时间戳Date date = new Date();System.out.println(date.getTime());}
}

但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况:

例如,util 和 sql 中都存在一个 Date 这样的类(同名不同包的类), 此时就会出现歧义, 编译出错:

import java.util.*;
import java.sql.*;public class Test{public static void main(String args){int[] array = {1,2,3,4,5,6};System.out.println(Arrays.toString(array));//使用Date类中的getTime方法得到一个毫秒级别的时间戳Date date = new Date();System.out.println(date.getTime());}
}

原因:util和sql中都有Date类,那我们在使用的时候就不知道到底是使用util中的Date类还是sql中的Date类

这时候为了不发生冲突,就只能使用完整的类名:

import java.util.*;
import java.sql.*;public class Test {public static void main(String[] args) {java.util.Date date = new java.util.Date();System.out.println(date.getTime());}
}

如果想要同时导入sql中的Date类和util中的Date类,那么其中一个只能用完整类名去访问而不用import语句(无论是用 * 或者显示指定导入类名的方式):

import java.util.Date;
import java.sql.Date;
//或者
//import java.util.*;
//import java.sql.*;public class Test {public static void main(String[] args) {Date date = new Date();System.out.println(date.getTime());java.sql.Date date2 = new java.sql.Date(10);}
}

---------------------------------------------------------------------------------------------------------------

可以使用import static导入包中静态的方法和字段:(但是一般不会这样写)

import static java.lang.Math.*; //意思是导入Math类中的所有静态成员(包括静态方法和静态变量),在后续代码中可以直接使用这些静态成员,而不需要写类名Math。public class Test {public static void main(String[] args) {double x = 30;double y = 40;double result = Math.sqrt(Math.pow(x,2)+Math.pow(y,2));//静态导入的方式写起来更方便一些double result1 = sqrt(pow(x,2)+pow(y,2));System.out.println(result1);}
}
自定义包

基本规则

  • 在文件的最上方加上一个 package 语句声明当前类在哪个包中.
  • 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.abc.www ).
  • 包名要和代码路径相匹配. 例如创建 com.abc.www 的包, 那么会存在一个对应的路径 com/abc/www 来存储代码.
  • 如果一个类没有 package 语句, 则该类被放到一个默认包中.

操作步骤

1. 在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包

2. 在弹出的对话框中输入包名, 例如 com.abc.www

3. 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可

4. 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了

5. 同时我们也看到了, 在新创建的 TestPackage.java 文件的最上方, 就出现了一个 package 语句

包的访问权限控制举例

例如,TestPackage类位于 com.abc.www 包中,在类中创建一个test方法:

如果想要在 Test类中使用TestPackage类中的方法,就需要导入包:

然后就可以在Test类中访问了:

常见的包
  • 1.java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
  • 2. java.lang.reflect:java 反射编程包;
  • 3. java.net:进行网络编程开发包。
  • 4. java.sql:进行数据库开发的支持包。
  • 5. java.util:是java提供的工具程序包。(集合类等) 非常重要
  • 6. java.io:I/O编程开发包。

二、static成员

static修饰的成员--静态成员(类成员)

使用前文中介绍的学生类实例化2个对象student1、student2,每个对象都有自己特有的名字、年龄,学号、班级等成员信息,这些信息就是对不同学生来进行描述的,如下所示:

假设2个同学是同一个班的,那么他们上课肯定是在同一个教室,那既然在同一个教室,那能否给类中再加一个成员变量,来保存同学上课时的教室呢?

答案是不行的。 之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体的学生。

而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份而是需要让所有的学生来共享。在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的;而且静态的成员只有一份,存在方法区当中。

当我们将className成员变量变成静态成员(类成员)后,访问方式有两种:

student1.className = "1班";//通过对象的引用访问
Student.className = "1班";//通过类的访问

很明显,我们使用类名来访问更加合理,静态的成员变量,也被叫做类成员,也就是说,这个变量属于类的,不是对象的,而且这个变量是储存在方法区的,通过实例化对象来访问静态成员是多此一举的(即 静态的 不依赖于对象,意味着不需要对象的引用来访问),所以最终结论就是用类名来访问静态成员

可以验证静态成员在所有实例化对象中是共享的:

在Student类中写一个方法:

(注意:类成员不需要this去表示要访问的是当前对象,它只拷贝一份,在所有的类的实例化对象中都是共享的)

(注意:如果通过类名去修改它,它的值也是可变的)

验证静态成员(类成员),是属于类的,而不是属于对象的(静态的 不依赖于对象)

按照我们前面所学习的,以下的代码想通过一个student对象引用访问className,即通过null引用访问calssName,当我们运行后会 抛出 NullPointerException 错误

但是,结果并没有出错误,而是运行出了结果:

这就说明 className 是一个类成员(静态成员),而不是实例成员

结论:

1.类成员属于类本身,而不是对象:

  • 类成员(静态成员)在类加载时就被分配内存,与是否创建对象无关。
  • 即使对象为 null,也可以通过引用访问类成员(编译器会转换为类名访问)。

2.实例成员属于对象:

  • 如果 className 是实例成员,则代码会抛出 NullPointerException。

3.正确的访问方式:

  • 类成员应该通过类名直接访问(例如 Student.className),而不是通过对象引用,以提高代码可读性和避免歧义

一个比喻:

想象类成员是一个教室里的公共广播喇叭。

  • 这个喇叭不属于任何一个学生(对象),而是属于教室(类)本身。
  • 喇叭只有一个(只拷贝一份)。
  • 每个学生都能听到(共享)这个喇叭的声音。
  • 任何被授权的人(如老师或任何一个学生)都可以去调整喇叭的音量(修改它的值),而一旦音量被调整,全班同学听到的音量都会同时改变。

static修饰成员变量

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。

【静态成员变量特性】

  1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
  2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
  3. 类变量存储在方法区当中
  4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)

在前面我们理解什么是静态成员(类成员)中,已经详细介绍了

格式:

[访问限定符] static [final] 数据类型 变量名 [= 初始值];

注意:如果加上 final 关键字,那么这个变量就变成了一个常量。一旦被赋值,其值就不能再被修改。常量名通常遵循全大写的命名规范。

[= 初始值] (初始值 - 可选):您可以在声明的同时给变量一个初始值。如果不给初始值,Java会赋予一个默认值(如数值类型为0,布尔类型为false,引用类型为null)

例如:

public static String className;

注意:静态成员变量不可以定义在方法中。静态成员变量必须作为类的成员,定义在类体内部、所有方法的外部。

static修饰成员方法

格式

[访问修饰符] static [返回类型] 方法名([参数列表]) {
// 方法体
[return 返回值;] // 如果返回类型不是void
}

一般类中的数据成员都设置为private,而成员方法设置为public,那设置之后,Student类中静态成员classRoom如何在类外访问呢?

在Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。

【静态方法特性】

1. 不属于某个具体的对象,是类方法

2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者

3.不能在静态方法中访问任何非静态成员变量

(原因请往下看)

4.静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用(表示当前对象)。

在静态方法中,不能够直接调用非静态方法,因为静态方法不依赖于对象,可以直接通过类名进行访问,但是非静态方法依赖对象,就例如show方法,需要通过对象的引用访问,即student1.show();

但是,也是有解决方法的,要想在静态方法中调用非静态方法,可以先实例化对象,然后通过对象的引用在静态方法中去访问非静态方法。

例如,前面我们在学习方法的时候,写一个方法实现两个数相加:

方法1:

如果add方法是一个静态方法,那么它就可以在同样是静态方法的main方法中直接调用

方法2:

如果add方法是非静态方法,而main方法也是可以调用非静态方法的,但必须先创建类的实例,然后通过对象的引用访问:

同理,想要在静态方法getclassName中访问非静态方法show,创建类的实例化:

那么我们就可以反过来解释为什么不能在静态方法中访问非静态成员变量:因为非静态成员变量依赖对象,需要通过实例化对象,然后通过对象的引用去访问。解决方法:想要在静态方法中访问非静态成员变量,只能通过创建实例化对象,然后通过对象的引用访问。

静态方法中不能使用 this关键字,因为this引用是表示要访问的当前对象的,在不依赖对象的静态方法中就不可以使用,就算创建实例化对象也不可以在静态方法中使用

5.非静态方法中可以调用静态方法

在同一个类中,静态方法可以直接调用(不需要类名),而在不同类中,通常通过类名调用。同时,非静态方法中调用静态方法也是直接调用(在同一个类中)或通过类名(在不同类中)。

(静态成员变量同理)

总结

  • 方法 或者 成员变量 的调用方式就 2种情况:
  1. 通过对象的引用调用
  2. 通过类名调用
  • 在静态方法中,不能直接使用任何非静态的成员变量和成员方法,要想使用,要通过实例化对象 (new对象),通过对象的引用访问
  • 在非静态方法中,可以直接使用静态的成员变量或者静态的成员方法
  • this关键字不能再静态方法中使用
  • 记住,静态的 不依赖于对象,属于类,不属于对象

static成员变量初始化

静态成员变量的初始化方式有4种:

就地初始化 、Setter方法初始化  、构造方法初始化、  静态代码块初始化 。

就地初始化

就地初始化指的是:在定义时直接给出初始值

Setter和Getter初始化

因为我们所创建的静态成员变量是被private修饰,所以它也是私有静态成员变量,可以通过创建setter方法和getter方法(自己创建或者快捷键都可),通过对象的引用去初始化值。

构造方法初始化

构造方法中初始化的是与对象相关的实例属性静态成员变量可以放在构造方法中来初始化,但是较少使用构造方法来初始化静态成员变量。

静态代码块初始化

那什么是代码块呢?继续往后看

三、代码块

代码块概念以及分类

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:

  1. 普通代码块
  2. 非静态代码块/实例代码块/构造代码块
  3. 静态代码块
  4. 同步代码块(后续讲解多线程部分再谈)

普通代码块

普通代码块:定义在方法中的代码块.

普通代码块也叫局部代码块,它没有条件,在代码块内声明的变量只在该块内有效,出了作用域就释放掉。

这种用法较少见

构造代码块

定义在类中,方法外边的代码块(不加修饰符)。也叫:实例代码块,非静态代码块。

构造代码块一般用于初始化实例成员变量。(与构造方法类似,都是在实例化对象的时候初始化的),没有条件

静态代码块

使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量

静态代码块定义在类中,方法的外边

看以下的两个初始化代码:

最终运行的结果是 "2班",即最终初始化的的结果是 2班:

这就说明这个静态代码块执行了,那这个静态代码块是怎么被触发的呢?

————当类加载的时候,就会被触发

那为何结果是 "2班"呢?

————因为这个静态代码块是static静态的,而就地初始化的className也是一个静态的成员变量,和定义的顺序有关系,就地初始化在前,静态代码块初始化在后,所以它最终输出的结果是"2班";而如果我们将两者的顺序调换,那么输出的就是 "1班"。

结论:静态代码块在类加载的时候就被执行了,如果出现多个静态代码块,都是静态的情况下,和定义的顺序有关系。

我们再看看构造代码块,它是在实例化对象的时候被触发的:

从运行的结果我们发现:静态代码块和构造代码块均被执行了,说明当静态代码块和构造代码块同时存在时,都会被执行(要实例化对象),而且是先执行静态代码块,再执行构造代码块和它们的前后顺序无关。

我们还定义了构造方法,那构造方法又是什么时候被执行呢?

从运行结果我们知道,构造方法在最后被执行了,即先静态代码块,再构造代码块,后构造方法和定义的前后顺序无关。

因为是先构造代码块,后构造方法,那么最终输出的name的结果就是"zhangsan",构造方法初始化的结果:

如果是无参数的构造方法,那么执行结果就是"xiaohua",因为执行完构造代码块之后,执行构造方法的时候,根本就没有对name变量进行修改,所以它的值就只能是构造代码块初始化的值:

如果构造代码块不和构造方法比,而是和name成员变量比,那么此时它们都是非静态的,即是实例的,它们就和它们的定义的前后顺序有关了:

当非静态成员变量name先执行,构造代码块后执行,输出结果还是"xiaohua":

如果前后顺序调换,输出的结果就是"zhaohua":

static成员变量初始化总结:

  1. 如果类中有多个静态代码块,即都是静态的,和定义顺序有关系
  2. 如果类中有多个构造代码块/实例代码块,即都是非静态的,和定义顺序有关系
  3. 如果类中有多个构造方法,那就看在实例化对象的时候调用谁了

还有另种情况,如果我们创建两个实例化对象时:

输出结果:在执行第二个对象的时候,静态代码块不再执行了,说明了—— 静态的只会执行一次,意味着这个类只会被加载一次,无论实例化多少个对象,都是一样的。

注意事项:

  • 静态代码块不管生成多少个对象,其只会执行一次
  • 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
  • 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
  • 实例代码块只有在创建对象时才会执行

四、对象的打印

通过前面的代码可以知道,我们是通过一个show方法,通过对象的引用完成name等成员变量的输出。但是如果成员变量太多了,那么我们自己手写show方法将一个个的变量写出来,这样子太麻烦了,那可不可以编译器自己生成一个show方法呢? —— 答案是可以的

我们知道。当我们实例化完对象后,打印student引用时输出的是一个地址,因为student引用的是一个对象,即指向一个对象,所以它存放的是这个对象的地址:

如果我们自己调用show方法,则输出的就不是地址,我们使用编译器自己的show方法,即toString方法:

此时它生成了一个这样的代码,再次运行输出:

可以看到,打印的结果是一个字符串,并输出类name等成员变量和它们初始化的值,而不再是输出一个地址,我们先看看方法细节(按住ctrl的同时点击鼠标左键,查看方法细节):

可以发现默认调用的 toString方法的默认输出就是一个地址

如果想要默认打印对象中的属性 ,那么重写toString方法(自己写一个或者编译器生成一个)即可,就会调用重写的toString方法:

http://www.dtcms.com/a/354158.html

相关文章:

  • Redis集群介绍——主从、哨兵、集群
  • 单兵图传设备如何接入指挥中心平台?国标GB/T28181协议的20位ID有何含义?如何进行配置?
  • [手写系列]Go手写db — — 第二版
  • spring-boot-test与 spring-boot-starter-test 区别
  • 前端架构设计模式与AI驱动的智能化演进
  • 嵌入式学习日志————USART串口协议
  • 【开发便利】让远程Linux服务器能够访问内网git仓库
  • 目标检测基础
  • [系统架构设计师]论文(二十三)
  • 控制系统仿真之时域分析(二)
  • 计算机组成原理(13) 第二章 - DRAM SRAM SDRAM ROM
  • 通信原理(005)——带宽、宽带、传输速率、流量
  • 农业物联网:科技赋能现代农业新篇章
  • uC/OS-III 队列相关接口
  • Linux 命令浏览文件内容
  • 机器视觉的车载触摸屏玻璃盖板贴合应用
  • 【Bluetooth】【调试工具篇】第九章 实时抓取工具 btsnoop
  • [vcpkg] Windows入门使用介绍
  • 致远OA新闻公告讨论调查信息查询SQL
  • 模拟电路中什么时候适合使用电流传递信号,什么时候合适使用电压传递信号
  • 世界的接口:数学、心智与未知的协作
  • 【前端】jsmpeg 介绍及使用
  • Libvio 访问异常排查指南:从现象到根源的深度剖析
  • 专项智能练习(关系数据库)
  • 风锐统计——让数据像风一样自由!(九)——回归分析
  • FreeRTOS内部机制理解(任务调度机制)(三)
  • opencv学习笔记
  • 基于 Docker Compose 的若依多服务一键部署java项目实践
  • 【深度学习-Day 44】GRU详解:LSTM的优雅继任者?门控循环单元原理与PyTorch实战
  • sparksql的transform如何使用