导学

在之前的课程中,我们已经对Java的面向对象开发有了一些了解。那么本章节,我们就来看看面向对象三大特性之一的封装。
所谓封装,就是将类的某些信息隐藏在类的内部,不允许外部程序直接访问,只能通过该类提供的方法来实现对隐藏信息的操作和访问。
简单来说,既要隐藏对象的信息,也要留出访问的接口。
封装的特点在于:

  1. 只能通过规定的方法访问数据
  2. 隐藏类的实例细节,方便修改和实现

    封装的实现

    对于如何实现封装,我们可以通过以下三个步骤:
    1、将属性的访问修饰符改为private
    2、设置get与set方法,将属性提供出来以供使用
    2. Java封装 - 图1
    在之前的课程中,我们使用了宠物猫这个例子,那么这个宠物猫的年龄可以由我们自由的设置,那么如果我们给宠物猫的年龄设置为负值,则就不符合现实的逻辑了。
    控制属性不能随意修改

    修改属性的可见性

    1. public class Cat {
    2. //将属性的访问控制修饰符修改为private-限定只能在当前类内被访问
    3. private String name;
    4. private int month;
    5. private double weight;
    6. private String species;
    7. }

    private修饰的属性只能在当前类中进行操作和访问,在本类之外,是不允许被直接访问的。

    创建公有的get/set方法

    1. public class Cat {
    2. //1.将属性的访问控制修饰符修改为private
    3. private String name;
    4. private int month;
    5. private double weight;
    6. private String species;
    7. //2.创建公有的get/set方法
    8. public String getName() {
    9. return name;
    10. }
    11. public void setName(String name) {//传入相同的参数名与属性形成对照
    12. this.name = name;
    13. }
    14. public int getMonth() {
    15. return month;
    16. }
    17. public void setMonth(int month) {
    18. this.month = month;
    19. }
    20. public double getWeight() {
    21. return weight;
    22. }
    23. public void setWeight(double weight) {
    24. this.weight = weight;
    25. }
    26. public String getSpecies() {
    27. return species;
    28. }
    29. public void setSpecies(String species) {
    30. this.species = species;
    31. }
    32. }

    为了与其他程序进行对接,get与set方法需要保证为规定的格式:
    1、访问修饰符设置为公有的- public
    2、get方法需要访问与属性相同的返回值,方法名get开头+ 首字母大写的属性名
    3、set方法需要传入与属性类型相同的参数,并在方法中设置利用参数对属性赋值。set方法名和get格式一样
    4、get和set方法命名都遵循驼峰原则
    相关方法名的定义:

  • get + 首字母大写的属性名() :get 方法一般都是具有和属性数据类型一致的返回值,并且是没有形参的。
  • set + 首字母大写的属性名(参数):set 方法一般都是具有和属性数据类型一致的方法参数,返回值一般是 void。

如果一个属性只有get方法以供获取的话,我们说该属性就是一个只读属性。如果只有set方法以供操作的话,那么该属性就是一个只写属性。

在 Eclipse 中,可以通过快捷方式生成相关属性的 getter 和 setter 在 「source」-> 「generate getters and setters」

在get/set方法中加入属性控制

这一步并不是必须的,而是为了针对属性值进行合理的判断,防止对属性值的胡乱操作。

  1. /**
  2. * 针对年龄范围作出合理限制
  3. * @param month
  4. */
  5. public void setMonth(int month) {
  6. if(month < 0 || month >= 240) {
  7. System.out.println("年龄不合理,暂定为1");
  8. this.month = 1;
  9. } else {
  10. this.month = month;
  11. }
  12. }

访问和操作属性

借助set方法对属性进行赋值

  1. import com.dodoke.obj.animal.*;
  2. public class CatTest {
  3. public static void main(String[] args) {
  4. Cat one = new Cat();
  5. one.setName("凡凡");
  6. one.setMonth(3);
  7. one.setWeight(0.5);
  8. one.setSpecies("英短");
  9. System.out.println("昵称:" + one.getName());
  10. System.out.println("月份:" + one.getMonth());
  11. System.out.println("重量:" + one.getWeight());
  12. System.out.println("品种:" + one.getSpecies());
  13. }
  14. }

对于已经封装好的类,我们想要操作和访问其属性,只有利用其对外暴露的接口set/get方法,才能实现。

带参构造器中的属性控制

在之前的课程中,我们提到可以使用带参构造器实现对象实例化时候的属性设定。

  1. //保证类中无论何时都有一个无参构造器
  2. public Cat() {
  3. }
  4. public Cat(int month) {
  5. this.month = month;
  6. }
  7. ===============================
  8. //测试
  9. public static void main(String[] args) {
  10. Cat two = new Cat(-3);
  11. System.out.println(two.getMonth());
  12. }

我们发现上述代码执行之后,并没有完成对属性值的限制,这是因为在构造器中,我们直接对属性值进行了操作。所以,我们可以利用set方法中的属性限制,完成代码逻辑。
在构造器中对属性进行控制

  1. public Cat(int month) {
  2. //this.month = month;
  3. this.setMonth(month);
  4. }

static关键字

2. Java封装 - 图2
针对于主方法,我们之前也已经介绍过其中的一些知识点,本章节,我们来学习static关键字的作用。static代表着静态信息。
首先我们通过一段代码来看一下static的作用

  1. public class Cat {
  2. //1.将属性的访问控制修饰符修改为private
  3. private String name;
  4. private int month;
  5. private double weight;
  6. private String species;
  7. public static int price;//价格
  8. //2.创建公有的get/set方法
  9. public String getName() {
  10. return name;
  11. }
  12. public void setName(String name) {
  13. this.name = name;
  14. }
  15. public int getMonth() {
  16. return month;
  17. }
  18. /**
  19. * 针对年龄范围作出合理限制
  20. * @param month
  21. */
  22. public void setMonth(int month) {
  23. if(month < 0 || month >= 240) {
  24. this.month = 1;
  25. } else {
  26. this.month = month;
  27. }
  28. }
  29. public double getWeight() {
  30. return weight;
  31. }
  32. public void setWeight(double weight) {
  33. this.weight = weight;
  34. }
  35. public String getSpecies() {
  36. return species;
  37. }
  38. public void setSpecies(String species) {
  39. this.species = species;
  40. String str = "男";
  41. if(str.equals("男")) {
  42. System.out.println("性别为男");
  43. } else {
  44. System.out.println("性别为女");
  45. }
  46. }
  47. public Cat() {
  48. }
  49. public Cat(int month) {
  50. this.setMonth(month);
  51. }
  52. }
  1. public class CatTest {
  2. public static void main(String[] args) {
  3. Cat one = new Cat();
  4. one.setName("花花");
  5. one.setMonth(3);
  6. one.setWeight(0.5);
  7. one.setSpecies("英短");
  8. one.price = 2000;
  9. Cat two = new Cat();
  10. two.setName("凡凡");
  11. two.setMonth(1);
  12. two.setWeight(0.4);
  13. two.setSpecies("中华田园猫");
  14. two.price = 150;
  15. System.out.println("我叫" + one.getName() + ",我的售价是" + one.price);
  16. System.out.println("我叫" + two.getName() + ",我的售价是" + two.price);
  17. }
  18. }

上述代码运行之后,我们会发现两只猫的售价都是150。这里就是static的一个作用了。
static表示静态的,static修饰的属性称之为【类变量,静态变量】,方法称之为【类方法,静态方法】。
在Java程序中,static修饰的成员具有这样一个特征,无论该类最终实例化出来多少对象,静态成员都会共用一块静态空间。也就是说:
2. Java封装 - 图3
无论有多少宠物猫,对于价格而言,它们是共用同一块静态空间的。这也就是花花的价格起先是两千,而在凡凡的价格修改为150之后,两者的价格都被修改为150了。因为两者都在针对同一块内存空间进行操作。

对普通成员而言:当这个类的对象产生的时候,它的相关成员会产生,而当这个对象销毁的时候,这些成员就会进行资源释放。 对静态成员而言:从类第一次加载(jvm中的类加载器)的时候,静态成员就会产生(在创建对象之前就已存在),一直到这个类不在有任何对象被使用。也就是它彻底销毁的时候,静态资源才会进行资源的释放。所以静态成员的生命周期比较长寿。

所以,静态资源有着如下的特性:

  • 类对象共享
  • 类加载时产生,销毁时释放,生命周期长

静态资源的访问方式:

  • 对象.静态成员
  • 类.静态成员

只是对于使用对象.静态成员的方式调用静态成员,会出现警告。这是因为本质上,用static修饰的成员变量和方法,是属于的,而不是属于该类的实例(对象)。即静态内容可以被类所调用,也可以被对象调用,但这种方式不是推荐的方式(合法而不合理)。静态内容 在堆内存中单独开辟一块内存空间 ,多个对象调用赋值时,都是 针对同一块内存空间进行操作 ,静态内容最终的值是最后一个对象对它的赋值。

成员属性/方法 类属性/方法
属于类的成员(对象)的属性和方法 属于对象共有(类)的属性和方法

static的修饰内容

static用来修饰方法和属性

  1. public static class Cat {}//不能用在类的修饰上
  2. public void eat() {
  3. static int a = 5;//注意不能用来修饰局部变量
  4. }

静态内容与非静态内容的访问限制

1、普通成员方法,可以调用静态资源。静态资源之间可以相互调用,但是不能使用this调用
2、静态方法中不能出现非静态内容。

  1. 在成员方法中,可以直接访问类中静态成员 ```java public static void eat() { System.out.println(“小猫吃鱼”); }

public void run() { eat(); this.name = “妞妞”; //注意这边this表示的是正在调用该属性的对象 //其实也就是使用了==>对象.静态资源的调用方式 this.price = 20; System.out.println(“售价是” + Cat.price + “的” + this.name + “快跑”); }

  1. 测试:
  2. ```java
  3. public class CatTest {
  4. public static void main(String[] args) {
  5. Cat one = new Cat();
  6. one.setName("花花");
  7. one.setMonth(3);
  8. one.setWeight(0.5);
  9. one.setSpecies("英短");
  10. one.price = 2000;
  11. Cat.price = 3000;
  12. one.run();
  13. }
  14. }
  1. 静态方法中不能直接访问同一个类的非静态成员,只能直接调用同一个类中的静态成员。

    1. public static void eat() {
    2. run();//不能调用
    3. this.name = "胖虎";//静态方法中不能使用this
    4. name = "胖虎";
    5. price = 1500;//可以调用
    6. Cat cat = new Cat();
    7. cat.run();//这样才可以调用
    8. System.out.println("小猫吃鱼");
    9. }

    静态方法中如果想要访问非静态成员,只能通过对象实例化后,对象.成员方法/属性的方式访问

    代码块

    在Java中,如果在语句当中出现{ },这样的大括号对这就叫代码块
    普通代码块:出现在普通方法中的代码块{ }
    构造代码块:直接出现在类中{ },会在实例化对象时进行调用,先于构造器执行,多个构造代码块顺序执行。构造代码块常用于构造器的准备工作。
    静态代码块:直接出现在类中,使用static修饰static{ }

  2. 先于构造代码块执行,多个静态代码块顺序执行,无论实例化多少对象,只在第一次实例化对象时调用。

  3. 静态代码块中不能出现非静态的方法和属性,同样也不可以使用this。
  4. 静态代码块中常用来对静态属性进行赋值,可以将只执行一次的代码放置到静态代码块中

普通代码块——顺序执行

  1. public void run(String name) {
  2. {
  3. System.out.println("我是普通代码块1");
  4. }//出现在普通方法中
  5. System.out.println(name + "快跑");
  6. {
  7. System.out.println("我是普通代码块2");
  8. }
  9. }
  10. public static void main(String[] args) {
  11. Cat one = new Cat();
  12. one.run("花花");
  13. }
  • 构造代码块——创建对象时调用,优先于构造方法执行;多个构造代码块顺序执行;

    1. public class Cat {
    2. //1.将属性的访问控制修饰符修改为private
    3. private String name;
    4. private int month;
    5. private double weight;
    6. private String species;
    7. public static int price;//价格
    8. {
    9. System.out.println("我是构造代码块1");
    10. }//直接出现在类中
    11. //2.创建公有的get/set方法
    12. public String getName() {
    13. return name;
    14. }
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. public int getMonth() {
    19. return month;
    20. }
    21. /**
    22. * 针对年龄范围作出合理限制
    23. * @param month
    24. */
    25. public void setMonth(int month) {
    26. if(month < 0 || month >= 240) {
    27. this.month = 1;
    28. } else {
    29. this.month = month;
    30. }
    31. }
    32. public double getWeight() {
    33. return weight;
    34. }
    35. public void setWeight(double weight) {
    36. this.weight = weight;
    37. }
    38. public String getSpecies() {
    39. return species;
    40. }
    41. public void setSpecies(String species) {
    42. this.species = species;
    43. }
    44. public Cat() {
    45. System.out.println("我是宠物猫~");
    46. }
    47. {
    48. System.out.println("我是构造代码块2");
    49. }//直接出现在类中
    50. public Cat(int month) {
    51. this.setMonth(month);
    52. }
    53. public static void eat() {
    54. System.out.println("小猫吃鱼");
    55. }
    56. public void run(String name) {
    57. {
    58. System.out.println("我是普通代码块1");
    59. }//出现在普通方法中
    60. System.out.println(name + "快跑");
    61. {
    62. System.out.println("我是普通代码块2");
    63. }
    64. }
    65. }
  • 测试:

    1. public static void main(String[] args) {
    2. Cat one = new Cat();
    3. one.run("花花");
    4. }
    5. ========================
    6. 运行结果:
    7. 我是构造代码块1
    8. 我是构造代码块2
    9. 我是宠物猫~
    10. 我是普通代码块1
    11. 花花快跑
    12. 我是普通代码块2
  • 静态代码块——static修饰的代码块,优于构造代码块执行,多个静态代码块顺序执行;无论产生多少个实例,只调用一次。

    1. public class Cat {
    2. //1.将属性的访问控制修饰符修改为private
    3. private String name;
    4. private int month;
    5. private double weight;
    6. private String species;
    7. public static int price;//价格
    8. {
    9. System.out.println("我是构造代码块1");
    10. }//直接出现在类中
    11. //2.创建公有的get/set方法
    12. public String getName() {
    13. return name;
    14. }
    15. public void setName(String name) {
    16. this.name = name;
    17. }
    18. public int getMonth() {
    19. return month;
    20. }
    21. /**
    22. * 针对年龄范围作出合理限制
    23. * @param month
    24. */
    25. public void setMonth(int month) {
    26. if(month < 0 && month >= 240) {
    27. this.month = 1;
    28. } else {
    29. this.month = month;
    30. }
    31. }
    32. public double getWeight() {
    33. return weight;
    34. }
    35. public void setWeight(double weight) {
    36. this.weight = weight;
    37. }
    38. public String getSpecies() {
    39. return species;
    40. }
    41. public void setSpecies(String species) {
    42. this.species = species;
    43. }
    44. public Cat() {
    45. System.out.println("我是宠物猫~");
    46. }
    47. static{
    48. System.out.println("我是静态代码块");
    49. }
    50. public Cat(int month) {
    51. this.setMonth(month);
    52. }
    53. public static void eat() {
    54. System.out.println("小猫吃鱼");
    55. }
    56. public void run(String name) {
    57. {
    58. System.out.println("我是普通代码块1");
    59. }//出现在普通方法中
    60. System.out.println(name + "快跑");
    61. {
    62. System.out.println("我是普通代码块2");
    63. }
    64. }
    65. }
  • 仅希望执行一次的代码就可以放到静态代码块中,这样可以提高代码的执行效率。
    在普通代码块中可以操作类成员,但是在静态代码块中只能操作静态成员,如果想用需要先实例化对象,通过对象.成员调用。

    代码块中的变量

    1. public void run(String name) {
    2. {
    3. System.out.println("我是普通代码块1");
    4. }//出现在普通方法中
    5. System.out.println(name + "快跑");
    6. {
    7. System.out.println("我是普通代码块2");
    8. }
    9. }

    这样一段代码,实际上形成了三个作用空间。
    2. Java封装 - 图4
    在之前的局部变量的课程中,我们也讲过,一个作用空间中是不允许出现两个同名变量的。那么在代码块中,是否可以出现同名变量呢?

    1. public void run(String name) {
    2. {
    3. int temp = 12;
    4. System.out.println("我是普通代码块1,temp=" + temp);
    5. }//出现在普通方法中
    6. System.out.println(name + "快跑,temp=" +temp);//出错,temp的作用范围只在大括号内
    7. {
    8. int temp = 13;
    9. System.out.println("我是普通代码块2,temp=" +temp);
    10. }
    11. }

    在一个代码块运行结束的时候,代码块中的局部变量就会被垃圾回收机制自动回收。

    1. public void run(String name) {
    2. int temp = 14;//以下的temp定义都会出错。
    3. {
    4. int temp = 12;
    5. System.out.println("我是普通代码块1,temp=" + temp);
    6. }//出现在普通方法中
    7. System.out.println(name + "快跑,temp=" +temp);//出错,temp的作用范围只在大括号内
    8. {
    9. int temp = 13;
    10. System.out.println("我是普通代码块2,temp=" +temp);
    11. }
    12. }