类和对象基础(下)
1. 封装
1.1 封装的概念
面向对象程序(比如:java)三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是:屏蔽细节。
以手机作为例子进行介绍:手机,已经成为了我们日常生活中,不可分割的一部分,甚至绝大部分的事情,都可以用手机进行解决了。对于手机这样一个复杂的设备,提供给用户的就只是:开关机、通过手指与屏幕的触碰,让用户来和手机进行交互,完成日常事务。但实际上:手机真正工作的却是CPU、内存等一些硬件元件。
对于手机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过手指与屏幕手机内的APP进行交互即可。
所以,手机机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机键、可触碰屏幕,数据接口(充电口)等,让用户可以与计算机进行交互即可。
我们可以看到,这是我们平常使用的手机的充电口,这个口,可以使用数据线,用于手机的充电或者数据的传输,但除了这个口,就没有其他的方式可以用了。这就是生活中的封装。但是,在 java语言当中,如何体现封装呢?
在Java中,我们使用 private 关键字,来对类进行封装,把类内的细节(成员)屏蔽起来,提供一些公开的,可以访问的方法就可以了
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互
代码演示:
class Teacher {
private String name;
private int age;
// 构造方法
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
}
这就是利用private关键字,对类内的成员变量进行封装。
同时,被封装的成员变量,只能在 当前类内进行使用,在类外,无法进行使用。
如图所示:
那我们如何对这个成员变量,进行访问呢?
答:使用公开的方法。
如:
public class Student {
//使用private 修饰成员变量
private String name;
private int age;
private String StuNum;
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 getStuNum() {
return StuNum;
}
public void setStuNum(String stuNum) {
StuNum = stuNum;
}
public static void main(String[] args) {
Student student = new Student(); // 实例化一个对象
//通过类内的公共的方法(get×××和set××× ),对私有的成员变量进行访问。
student.setName("小明");
student.setAge(10);
student.setStuNum("1234");
//通过get×××方法,过去 成员变量,进行打印,验证是否通过 set××× 方法,成功进行了赋值
System.out.println(student.getName());
System.out.println(student.getAge());
System.out.println(student.getStuNum());
}
}
运行这段代码,我们可以看到,成功通过了公开的方法,对private修饰的成员变量进行了赋值,并打印出成员变量的信息。
总结:封装就是使用了private关键字,对成员方法或者成员变量进行了封装。
达到的效果:该成员方法或者成员变量,只能在类内进行使用,在类外不能使用!
访问方式:通过类内的公开的成员方法,进行访问。
但,private关键字 到底是什么呢? —> 访问修饰限定符
1.2 访问修饰限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问修饰限定符:
- public :可以理解为地球上的空气,是公共的,谁都可以用,公开的
- protected :受保护的,主要用于在继承当中,继承的章节会讲到
- default :什么都不写时的默认权限
- private :可以理解为是自己的钱,是私有的
这四种访问权限,可以对类内的成员的访问权限进行管理。
简单使用一下修饰限定符:
public class Computer {
private String cpu; // cpu
private String memory; // 内存
public String screen; // 屏幕
String brand; // 品牌---->default属性
public Computer(String brand, String cpu, String memory, String screen) {
this.brand = brand;
this.cpu = cpu;
this.memory = memory;
this.screen = screen;
}
public void Boot(){
System.out.println("开机~~~");
}
public void PowerOff(){
System.out.println("关机~~~");
}
public void SurfInternet(){
System.out.println("上网~~~");
}
}
public class TestComputer {
public static void main(String[] args) {
Computer p = new Computer("联想", "i7", "1T", "2560*1440");
System.out.println(p.brand); // default属性:只能被本包中类访问
System.out.println(p.screen); // public属性: 可以任何其他类访问
// System.out.println(p.cpu); // private属性:只能在Computer类中访问,不能被其他类访问
}
}
一般来说,成员变量设置为private,成员方法设置为public。
具体访问权限的范围,如图所示:
看到这张图,我们或许会有很多疑问:什么是包?什么是不同包?什么是子类,什么又是非子类?
接下来,我们就介绍一下包。
至于 子类 相关的概念,继承的章节会介绍到。
1.3 封装的扩展–包
1.3.1 包的概念
如何理解包呢? —> 可以简单理解为是一个文件夹。
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
1.3.2 导入包中的类
Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date类.
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是这种写法比较麻烦一些, 可以使用 import语句导入包.
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果需要使用 java.util 中的其他类, 可以使用 import java.util.*
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况.
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.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());
}
}
可以使用import static导入包中静态的方法和字段
import static java.lang.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 result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
注意事项: import 和 C++ 的 #include 差别很大. C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要.import 只是为了写代码的时候更方便. import 更类似于 C++ 的 namespace 和 using
总结:当我们编写java代码的时候,不用刻意的去导包,一般当你使用java里已经写好的类的方法时,编译器会帮你自动导包,当你类与类之间继承的时候,如果是不同包的情况,编译器也会自动的帮你导包。
1.3.3 自定义包
基本规则:
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式
(例如:com.CSDN.demo1 ) - 包名要和代码路径相匹配. 例如创建 com.CSDN.demo1 的包, 那么会存在一个对应的路径 com/CSDN/demo1 来存储代码.
- 如果一个类没有 package 语句, 则该类被放到一个默认包中.
操作步骤:(我的IDEA是下载了文字汉化的插件,所以文字是中文的,各位的默认都是英文,但是选项的位置是一样的,对应一下即可)
-
在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包
-
在弹出的对话框中输入包名, 例如 com.CSDN.demo1,点击回车,就创建好了一个包
-
在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可.
-
此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了
-
同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句
1.3.4 包的访问权限控制举例
Computer类位于com.CSDN.demo1包中,TestComputer位置com.CSDN.demo2包中:
package com.CSDN.demo1;
public class Computer {
private String cpu; // cpu
private String memory; // 内存
public String screen; // 屏幕
String brand; // 品牌 ---> default属性
//构造方法
public Computer(String brand, String cpu, String memory, String screen) {
this.brand = brand;
this.cpu = cpu;
this.memory = memory;
this.screen = screen;
}
public void Boot(){
System.out.println("开机~~~");
}
public void PowerOff(){
System.out.println("关机~~~");
}
public void SurfInternet(){
System.out.println("上网~~~");
}
///
package com.CSDN.demo2;
import com.CSDN.demo1.Computer;
public class TestComputer {
public static void main(String[] args) {
Computer p = new Computer("联想", "i7", "1T", "2560*1440");
System.out.println(p.screen);
// System.out.println(p.cpu); // 报错:cup是私有的,不允许被其他类访问
// System.out.println(p.brand); // 报错:brand是default,不允许被其他包中的类访问
}
}
// 注意:如果去掉Computer类之前的public修饰符,代码也会编译失败
1.3.5 常见的包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
2. static 成员
2.1 再谈学生类
我们之前写过的学生类,他们每个人都有独特的名字,年龄,学号,但是,描述学生的信息里,一定还会有班级,如下代码:
class Student {
private String name;
private int age;
private String stuNum;
private String className;
public Student(String name, int age, String stuNum, String className) {
this.name = name;
this.age = age;
this.stuNum = stuNum;
this.className = className;
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student("zhangsan",10,"1234","1班");
Student student2 = new Student("lisi",12,"12348","1班");
}
}
再看看实例化的两个对象 student1和student2,在内存当中的分配:
这两个对象,是同一个班的,那么,我们可以把 “1班” 提取出来吗? 可以。
但是,怎么提取出来呢?—> 用 static 关键字!!!
在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
2.2 static修饰成员变量
2.2.1 静态成员变量的概念
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。
2.2.2 静态成员变量的特性
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 类变量存储在方法区当中
3. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
4. 静态成员变量的访问方式:
4.1 可以通过 当前对象的引用 进行访问,如 student1.className;(可行,但是,不推荐!!!!!)
4.2 通过 类名 进行访问,如:Student.className;(更推荐这种,因为静态成员变量,属于类变量,不是对象)
代码演示:
class Student {
private String name;
private int age;
private String stuNum;
public static String className;
public Student(String name, int age, String stuNum) {
this.name = name;
this.age = age;
this.stuNum = stuNum;
}
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 getStuNum() {
return stuNum;
}
public void setStuNum(String stuNum) {
this.stuNum = stuNum;
}
}
public class Main {
public static void main(String[] args) {
//static 修饰成员变量---> 静态成员变量
Student student = new Student("zhangsan",10,"1234");
//通过 当前对象的引用 进行访问
student.className = "1班";//编译器不会报错,但是,不推荐这种访问方式,
//通过 类名 进行访问
Student.className = "2班";
student.setName("小黄");
student.setAge(10);
System.out.println(" 姓名:"+ student.getName()+" 年龄: "+
student.getAge()+" 班级:"+ Student.className);
//注意:静态的---> 不依赖与对象 意味着,不需要当前对象的引用进行访问
}
}
从这段代码我们可以看到,当我第一次用当前对象的引用进行访问,给予赋值为"1班"后,第二次用 类名 的方式进行访问赋值为"2班",最后的输出结果是 “2班”;
总结:静态的,不依赖于对象。 也就是说,不需要对象的引用来访问!
验证代码如下:
class Student{ //现在将这三个成员变量,使用修饰限定访问符private,使得这三个变量只能在 类内 进行使用
private String name;
private int age;
private String classnum;
public static String classname;//定义了一个静态的公开成员变量 classname
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 getClassnum() {
return classnum;
}
public void setClassnum(String classnum) {
this.classnum = classnum;
}
}
public static void main(String[] args) {
// name是依赖对象的,会造成空指针异常
// Student student = null;//现在 这个引用的指向是随机的,没有实例化对象
// System.out.println(student.getName());
// 出现空指针异常,因为没有一个准确的地址,
// 所以,找不到这个对象当中的成员方法getName()
// Student student1 = null;
// student1.classname = "1班";
// System.out.println(student1.classname);//正常打印 1班
// //说明了 静态的成员变量(成员方法),不依赖于对象,所以不会出现空指针异常
}
分别执行这两段小代码,运行结果如下:
代码练习:体验一下,两种访问静态成员变量的方式
class Student{
public static int count;
}
public static void main(String[] args) {
//体验一下,静态成员变量的访问的两种方式。
Student student1 = new Student();
student1.count++;
Student student2 = new Student();
student2.count++;
Student student3 = new Student();
student3.count++;
Student.count--;
System.out.println(Student.count);//结果是 2
//无论是通过当前对象的引用去访问 静态成员变量,还是通过 类名 去访问静态成员变量,都可以访问到
//但是,静态成员变量 是属于 类变量,不属于对象的, 使用 类名 去访问 更加合理。
}
2.3 static修饰成员方法
一般类中的数据成员都设置为private,而成员方法设置为public,那设置之后,Student类中classRoom属性如何在类外访问呢?
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306";
// ...
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.classRoom); //Error
}
}
//编译失败:
//Error:classRoom 在 Student类 中是 private 访问控制
那static属性应该如何访问呢? —> 一般是通过 静态方法 进行访问。
2.3.1静态方法的概念
Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。
2.3.2 静态方法的特性
1. 不属于某个具体的对象,是类方法
2. 访问方式:
a.可以通过 当前对象的引用 进行访问 (可行,但是,不推荐!!!!!)
b.通过类名 进行访问 (更推荐这种,因为静态成员,属于类,不属于对象!!!)
3. 不能在静态方法中访问任何非静态成员变量
原因:静态的 不依赖对象,属于类! 非静态的,依赖对象,需要通过对象的引用去调用访问。(前面已经有验证代码验证过。)
在age仍是非静态的情况下,上面代码如何改,才正确呢?
答:通过实例化一个对象,通过对象的引用去调用。
4. 不能在静态方法中访问任何非静态成员方法
原因和解决方式,同 第三点 一样。
5. 静态方法里面,不能使用this关键字
原因:前面的this关键字我们介绍过,当我们通过对象去调用非静态成员方法时,方法的第一个参数,是隐藏的this参数,表示为当前对象的引用,所以,我们可以在非静态方法里面,使用this。 但是,调用静态方法时,我们不是用对象的引用去调用的,就算你是用对象的引用去调用静态方法(不推荐这种方法!!!),是不会传递当前对象的引用的,也就是,静态方法的形式参数列表里,没有this,所以,你无法在静态方法里使用this。
6. 仅限当前类内,类内的静态方法或者非静态方法里面,可以直接使用类内的静态方法
总结:
把握住一点:静态的,不依赖对象,属于类,不属于对象!
2.4 static成员变量初始化
静态成员变量的初始化方式:
1.就地初始化(定义时,直接赋值)
public static int age = 10;// 就地初始化
2.通过 get 和 set 方法
public static int getAge() {
return age;
}
public static void setAge(int age) {
Dog.age = age;
}
3.构造方法(比较少用)
因为构造方法,是用过 this.成员变量 来初始化的,
所以,不建议使用构造方法来对静态成员变量进行初始化
public Dog(int age) {
this.age = age;
}
4.代码块
static { // 静态代码块
age = 10;
}
看到这,想必你们会疑惑,什么是代码块? 往下看!
3. 代码块
3.1 代码块概念以及分类
使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造代码块(也叫实例代码块)
- 静态代码块
- 同步代码块(这里不解释,涉及到javaEE多线程的知识了)
3.2 普通代码块
普通代码块:定义在方法里面的代码块。
如:
public class Main1 {
public static void main(String[] args) {
//普通代码块
{
int a = 10;
System.out.println("a="+10);
}
int b = 20;
System.out.println("b="+20);
}
}
这里有一个需要注意的知识点:如果在你在任意代码块内,定义了一个 局部变量,那么,这个局部变量的作用域,就仅仅在该代码块内。
如:
所以我们可以看到,在普通代码块的外面,是无法辨别变量 a 的,因为变量a的作用域仅仅在普通代码块里面。
3.3 构造代码块
构造代码块:定义在类内的代码块,也叫 实例代码块。
作用:构造代码块一般用于初始化成员变量。
执行条件:只有在创建对象的时候才会执行。
class Student {
public String name;
public int age;
public String StuNum;
//实例代码块(构造代码块)
{
this.name = "小华";
this.age = 10;
this.StuNum = "1234";
}
}
public class Main1 {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
}
}
这段代码的执行结果:
我们可以看到,在代码中,成员变量 name在定义的时候,是没有被赋值的,类内也没有构造方法,只有我们说的构造代码块。现在的打印结果已经表明,构造代码块已经被执行了,是在创建对象的时候,执行的。
那么,肯定又会有人有疑问:如果我类内同时存在构造方法和构造代码块,他们的执行顺序是什么呢? —> 介绍完静态代码块后,会有介绍的。
3.4 静态代码块
静态代码块:使用static定义的代码块称为静态代码块,同时是定义在类内的,不可以在方法里定义。
作用:一般用于初始化静态成员变量。
执行条件:类被加载的时候执行,且仅执行一次。
class Student {
public static int age;
// 静态代码块
static {
// this.age = 12; 静态的里面,没有 this !
age = 12;
}
}
public class Main1 {
public static void main(String[] args) {
System.out.println(Student.age); // age是静态成员变量,需要使用 类名 去访问。
}
}
执行这段代码即可输出 12 。
3.4 代码块的注意事项
- 静态代码块不管生成多少个对象,其只会执行一次,因为类,只会被加载一次。
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
- 实例代码块只有在创建对象时才会执行
3.5 代码块和构造方法的执行顺序
3.5.1 相同类型代码块的执行顺序(因为构造方法只能发生重载,没有相同的,所以对构造方法不适用)
拿静态代码块来举例:
结论:同类型的代码块的执行顺序和定义的先后顺序有关。
3.5.2 静态代码块,实例代码块,构造方法的执行顺序
首先,我们直接看代码,看结果。
class Student {
public static String name;
//构造方法
public Student() {
Student.name = "张三";
System.out.println("构造方法执行了");
}
//实例代码块
{
name = "李四";
System.out.println("执行了实例代码块");
System.out.println(name);
}
//静态代码块
static {
name = "王五";
System.out.println("执行了静态代码块");
System.out.println(name);
}
}
public class Main1 {
public static void main(String[] args) {
Student student = new Student();
System.out.println(Student.name); // name 是静态成员变量,需要使用 类名 去访问。
}
}
由此,可得出结果:
执行顺序:静态代码块 --> 实例代码块 --> 构造方法,与定义顺序没有关系。
原因:由于java代码执行的时候,类会最先加载,此时,静态代码块执行了,所以,静态代码块,最先执行,但是,一个java代码,只会加载一次,所以,静态代码块,也只会执行一次,哪怕你定义了很多个类!
4. 内部类
由于这里的介绍,我会涉及到接口去介绍,所以,后面的接口出完以后,会单独出一篇介绍内部类。
5. 对象的打印
5.1对象的打印操作
对象的打印,也就是打印对象的信息。
之前,我们打印对象的信息,都是在类内写一个show方法,然后在main方法里面调用show方法,显示对象的信息,但是,当这个类的成员变量很多,那就不适合用这样的方式去显示对象的信息了。
我们用一种更为快捷简便的方式 —> 写ToString方法。然后在main方法里,直接使用对象的引用,进行打印。
打印对象信息的方式:System.out.println(对象的引用);
演示代码如下:
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("小黄",10);
System.out.println(dog);
}
}
运行结果如图所示:
所以,通过这样的方式,我们就能快速的打印对象的信息了。
5.2 快速生成toString() 方法
如何快速生成 toString() 方法? 答:Alt + Insert 或者点击鼠标右键,选择Generate
第一种:Alt + Insert 方式:
步骤:电脑键盘 同时按下 Alt + Insert,出现这个界面
选择toString(),选中你要打印的属性
第二种方式:鼠标点击右键,选中 Generate
剩下的,就和第一种的操作步骤,一模一样!
至于,为什么写了toString() 方法,是这样的效果,得运用到 动态绑定的知识,后面,多态的章节会介绍。
6. 总结
类和对象,可以说是java编程的基石,掌握好这些知识的同时,还要多去写代码,实现这些知识点,这样,会对这些知识点有更深度的理解,同时也会应用起来,而不是停留在概念上。
学习一门编程语言,是要求我们用编程去解决问题,我们要多敲代码,不要只看概念,眼高手低!!!