总结
本章写了封装、static成员以及代码块。
一、封装
1.封装的概念
封装简单来说就是被密封起来(不让我们看见的东西),即被隐藏。
对于用户来说,并不需要关心的类,所实现的细节就会被封装(隐藏)。
犹如密封的纸盒子
2.封装的实现方式
1.隐藏内部状态(属性私有化)
将类的成员变量(属性)用private修饰,使其只能在当前内部访问,外部无法直接访问修改或者读取。
2.暴露公共接口(提供访问方法)
通过public修饰的getter(读取)和setter(修改)方法,对外提供属性的访问入口,并在方法中可以添加逻辑校验,控制数据的合法性。
实例:
定义了一个People类中含有封装
public class People {public String name;
// 1.属性私有化(外部无法直接访问)private int age;/*private只能在当前类被访问,当前类的私有属性*/// 2.提供公共的getter方法(读取属性)public int getAge() {return age;}// 3.提供公共的setter方法(修改属性可添加校验逻辑 )public void setAge(int age) {
// 校验年龄为合理范围内if (age >= 0 && age <= 200) {this.age = age;}else {System.out.println("年龄无效");}}
}
再次定义了一个测试类运行它
public class Test {public static void main(String[] args) {People people = new People();people.name = "小华";
// 无法直接访问private属性people.setAge(23);System.out.println("请告诉我这个人叫什么?"+" "+people.name);System.out.println("多少岁?"+" "+people.getAge());}
}
getter方法也可以叫做取值方法,setter方法也可以叫做赋值方法。上一篇文章有详细的介绍5.Java类与对象。
3.封装的核心作用
1.数据的安全性
禁止外部直接修改属性通过setter方法的校验逻辑保证数据的合法。举个例子:银行里面存的钱,他人看不见我所存的具体金额,我也修改不了我的数据,这样就保证了社会的稳定,也保证了我的钱的安全性。
2.隐藏实现的细节
外部只需要知道如何通过公共方法使用类,无需关心内部属性的存储方式或逻辑降低耦合度。
3.代码的可维护性
若内部实现需要修改(如校验规则调整),只需修改类内部的方法,外部调用代码无需变动。
4.控制访问权限
3.访问权限控制符
public(公共的):全局可见,可以理解成一个人的外貌特征,谁都可以看见
protected:本包或者子类可见,主要是在继承中
default(包访问权限):本包可见,对于自己家族(同一个包中)不是秘密,对于他人来说就是隐私
private:仅本类可见
4.包
为了更好的管理类,把多个类搜集在一起成为一组,称为软件包。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口的很好的组织方式。使用包主要是避免类名的冲突、便于代码管理和访问控制,确定类的唯一性。
假如两个程序员不约而同地建立了 Employee类。 只要将这些类放置在不同的包中, 就不会产生冲突。
1.本质:包其实就是相当于类的“文件夹”,通过层级结构(类似文件系统的目录)组织类。
2.命名规范:通常使用小写字母,采用反转的域名作为前缀(避免全局冲突),例如:com.exmple.myapp。
层级之间用 . 分隔,对应文件系统的目录层级(如com/example/myapp)。
3.包的声明
在Java源文件的第一行,用package语句声明该类所属的包。犹如上图。
若未声明package,类会被放入默认包(不推荐,易导致冲突)
5.导入包中的类
一个类可以使用所属包中的所有类, 以及其他包中的公有类(public class)。我们可以采用两种方式访问另一个包中的公有类。
1.在每个类名之前添加 完整的包名 + 类名
2.使用 import 语句(一旦使用了 import 语句,在使用类时,就不必写出包的全名了)
可以使用 import 语句导人一个特定的类或者整个包。import 语句应该位于源文件的顶部(但位于 package 语句的后面)。
3.导入整个包的所有类(不推荐,可能引发冲突)
import com.test.demo1.*;//导入demo1这个包中所有的类
在大多数情况下, 只导入所需的包, 并不必过多地理睬它们。但在发生命名冲突的时候, 就不能不注意包的名字了。 例如,java.util 和java.sql 包都有日期 ( Date) 类。 如果在程序中导入了这两个包:
import java.util.*;
import java.sql.*;
就会报错
此时编译器无法确定程序使用的是哪一个 Date 类。
那我们怎么解决?
可以采用增加一个特定的 import 语句来解决这个问题:
这样就不会报错。
如果这两个 Date 类都需要使用, 又该怎么办呢?
答案是,在每个类名的前面加上完整的包名。
4.静态导入
import 语句不仅可以导人类,还增加了导人静态方法和静态域的功能。
就可以使用 Math 类的静态方法和静态域,而不必加类名前缀
import static java.lang.Math.*;
public class TestDemo3 {public static void main(String[] args) {double a = 1.69;double b = 1.96;
// 不导入静态包的写法
// double sqrtSum = Math.sqrt(a)+Math.sqrt(b);/*(sqrt是根号)1.3+1.4*/double sqrtSum = sqrt(a)+sqrt(b);System.out.println(sqrtSum);}
}
5.常见的包
1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。核心类(String、Object、基本类型包装类)
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:是java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包,输入输出类(文件操作、流等)
二、static成员
1.static修饰成员变量
static修饰的成员变量,被称为静态成员变量。
静态成员变量最大的特性:不属于具体的对象,是所有对象所共享的。
1.特性
①属于类的本身,而非某个具体的对象,所有对象共享一份数据。
②生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
③即可通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
public class Cat {public String name;public int age;public String color;public double weight;public static int foot = 4;public void eat(){System.out.println(name+"正在吃猫粮");}public void miao(){System.out.println(name+"会喵喵叫");}public static void main(String[] args) {Cat cat1 = new Cat();Cat cat2 = new Cat();cat1.name = "布丁";cat2.name = "大橘";
// 1.通过类名访问System.out.println(Cat.foot);
// 2.通过对象访问System.out.println(cat1.foot);System.out.println(cat2.foot);}
}
④类变量存储在方法区内
2.static修饰成员方法
1.定义
用static修饰的方法,属于类,不依赖于对象存在。
静态方法代表类级别的行为,而不是某个对象的行为。
2.访问方式
通过类名.方法名调用,不能直接访问非静态成员(非静态成员属于对象)
注意:静态方法中不能使用this或者super(因为与对象无关,而this和super需要传递参数)
3.static成员变量初始化
注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性
1. 就地初始化
就地初始化指的是:在定义时直接给出初始值
2.静态代码块初始化
下方有讲
三、代码块
1.普通代码块
定义在方法里面的代码块(直接使用{}定义)
public class TestDemo4 {public static void main(String[] args) {{
// 直接定义在{}里面的代码块,普通代码块int a = 10;System.out.println(a);}int a = 100;System.out.println(a);}
}
2.构造块
构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
public class Student {
// 实例变量public String name;public int age;public String grage;public double height;public double weight;public void doClass(){System.out.println(name+"正在学习");}{
// 2.实例代码块this.name = "小杨";this.age = 18;this.grage = "高三";this.height = 180.4;this.weight = 130.5;System.out.println(name+"正在吃饭");}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", grage='" + grage + '\'' +", height=" + height +", weight=" + weight +'}';}public static void main(String[] args) {Student student = new Student();System.out.println(student.toString());}
}
注意:实例代码块只有创建对象时才会执行
3.静态块
定义:使用static定义的代码块
静态块是Java类中用于初始化静态成员的特殊代码块,它在类加载时自动执行。静态块的主要特点包括:
-
语法结构: static { // 初始化代码 }
-
执行时机:
- 在类被首次加载时执行
- 在静态变量初始化之后执行
- 在main方法执行之前执行
-
重要特性:
- 静态代码块不管生成了多少个对象,其只会执行一次
- 静态成员变量是类的属性,因此在JVM加载类时开辟空间并初始化的
- 每个类可以有多个静态块,按声明顺序执行(合并)
- 不能访问非静态成员
- 不能抛出已检查异常
- 不能包含return语句
public class Cat {public String name;public int age;public String color;public double weight;public static int foot = 4;public static int averageLifespan;// 静态代码块:初始化静态属性,类加载时执行static {System.out.println("Cat类正在加载...");averageLifespan = 15;/*假设*/System.out.println("静态代码已执行:初始化寿命为"+averageLifespan+"年");}
// 构造方法(创建实例时执行)public Cat(String name) {this.name =name;System.out.println("创建了猫:"+name);}public static void main(String[] args) {// 第一次使用Cat类静态代码块执行Cat cat = new Cat("布丁");System.out.println("猫平寿:"+averageLifespan+"年");
// 静态代码块不再执行System.out.println("=====");Cat cat1 = new Cat("犀利哥");}}
Qustion:当静态代码块和构造代码块(实例代码块)以及构造方法同时存在时,怎么被执行的?其顺序是怎样的?
思考一下,下面的代码将被怎么执行?
public class Dog {public String name;public String species;public String color;public int age;static {System.out.println("静态代码块2");}static {System.out.println("静态代码块1");}static {System.out.println("静态代码块3");}{System.out.println("实例代码块2");}{System.out.println("实力代码块1");}public Dog(String name, String species, String color, int age) {this.name = name;this.species = species;this.color = color;this.age = age;System.out.println("构造方法");}public static void main(String[] args) {Dog dog = new Dog("小花","中华田园犬","黑白",12);System.out.println("=============下面是第二次执行");Dog dog1 = new Dog("小黄","中华田园犬","黄色",13);}
}
其结果是这样的
第一次时
静态代码块被执行(按顺序执行)
实例代码块被执行
构造代码块被执行
第二次
实例代码块被执行
构造代码块被执行
即静态代码块只被执行一次。
4.同步代码块
四、方法toString
toString方法是Object类的一个方法,用于返回对象的字符串表示。方便调试、打印或者日志记录。
简单来说toString方法就像是构造方法的输出语句,即可以直接输出像Student类中的name, age, grage , height , weight 这些即成员属性。
public class Student {
// 实例变量public String name;public int age;public String grage;public double height;public double weight;public void doClass(){System.out.println(name+"正在学习");}{
// 2.实例代码块this.name = "小杨";this.age = 18;this.grage = "高三";this.height = 180.4;this.weight = 130.5;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", grage='" + grage + '\'' +", height=" + height +", weight=" + weight +'}';}public static void main(String[] args) {Student student = new Student();System.out.println(student.toString());}
}
toString 方法可以在IDEA中有快捷生成方法键
就可以生成了。
1.默认行为(不重写)
我们可以点击输出中的println,他来看看他的底层代码(Ctrl键+鼠标右击)
我们进入了他的代码中,再点击Object类看看(Ctrl键+鼠标右击)
就进入到了Object类中,这个就是Object类中默认的toString实现方式。可以看见打印对象时,会输出类名( getClass() )全限定名(.getName() )@ 哈希码( Integer.toHexString(hashCode() )
可以看见默认行为,其可读性差,仅能区分“不同对象”,无法体现对象具体数据。
2.为什么重写ToString 方法
重写后,可自定义对象的字符串格式,让打印日志输出更直观,直接体现对象的属性值,方便调试和理解对象状态。
可以看一下不重写toString时是这样:
重写之后就是这样了(上方有源代码)