程序员编程笔记互联网科技

2-Java面向对象-封装

2018-08-03  本文已影响42人  天涯明月笙

面向对象的三大特征: 继承,封装,多态。

封装:

将类的某些信息隐藏在类内部, 不允许外部程序直接访问;通过该类提供的方法来实现对隐藏信息的操作和访问;

隐藏对象的信息 同时 留出访问的接口

生活中的案例:

ATM机,我们可以通过它存取款、转账、余额查询操作。钞票是ATM内的重要信息,但我们在外部是无法直接看到的,这就是ATM机对于钞票这个重要信息的隐藏。但是ATM拥有操作屏, 插卡口,取钞口等,用户通过简单的操作就可以实现隐藏钞票的操作。

特点:

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

封装的代码实现(上)

private 加在属性上,表明这个属性只能在当前类内被访问。

实现步骤: 修改属性的可见性,设为private; 创建getter/setter方法,设为public,用于属性的读写;在getter/setter方法中加入属性控制语句(对属性值的合法性进行判断:性别只能是男和女啊,年龄只能是正值啊)

宠物猫的年龄应该是正数。

  1. 修改属性的可见性
    private String name;
    private int age;
    private float weight;
    private String species;

private表示这个属性只能在Cat类内部进行访问。

可以看到此时这些属性已经无法再类外直接通过对象进行访问了。

访问修饰符: private public

  1. 还有什么呢?

  2. 访问权限是怎样的?

  3. 设置公有的getter/setter方法隐藏; 在get/set方法中添加对属性的限定。

public String getName() {
        return name;
    }

public void setName(String name) {
        this.name = name;
    }
可以通过快捷键:

进行快速的生成。

public String getName() {
        return "我是一只名叫"+name+"小猫";
    }

里面可以加入自己的一些逻辑,包装。下面是封装之后的使用:

        Cat oneCat = new Cat();
        oneCat.setAge(10);
        oneCat.setName("花花");
        oneCat.setSpecies("中华田园猫");
        oneCat.setWeight(1000);

        System.out.println("年龄: "+ oneCat.getAge());
        System.out.println("昵称: "+ oneCat.getName());
        System.out.println("体重: " + oneCat.getWeight());
        System.out.println("品种: " + oneCat.getSpecies());

运行结果:

封装代码实现下

注意只有getXXX方法的属性是只读属性;只有setXXX方法的属性是只写属性。下面我们在set方法中加入一些验证

    public void setAge(int age) {
        if (age <0){
            System.out.println("猫的年龄必须大于0");
        }else{
            this.age = age;
        }
    }
  
  // 调用
  oneCat.setAge(-1);

可以通过异常处理优化程序,后面会详细介绍。如果在构造函数中不使用get set 而是直接赋值,可以正常运行,但是不推荐。因为写在get/set里的验证逻辑将不会被执行。

public Cat(int age){
  this.age = age;
}

Cat oneCat = new Cat(-1);
System.out.println("年龄: "+ oneCat.getAge());

因此推荐在构造方法中也同样使用get/set

编程练习

编写自定义类实现图书信息设置,运行参考效果如下所示:

属性: 书名、作者、出版社、价格
方法: 信息介绍

package cn.mtianyan.book;

public class Book {
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPulishingHouse() {
        return pulishingHouse;
    }

    public void setPulishingHouse(String pulishingHouse) {
        this.pulishingHouse = pulishingHouse;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        if(price < 10){
            System.out.println("图书价格最低10元");
            this.price =10;
        }else{
            this.price = price;
        }
    }

    private String title;
    private String author;
    private String pulishingHouse;
    private float price;

    public Book(String title,String author,String pulishingHouse,float price){
        this.setTitle(title);
        this.setAuthor(author);
        this.setPulishingHouse(pulishingHouse);
        this.setPrice(price);
    }
    public void info(){
        System.out.println("书名: " + this.getTitle());
        System.out.println("作者: " + this.getAuthor());
        System.out.println("出版社: " + this.getPulishingHouse());
        System.out.println("价格: " + this.getPrice());
    }
}
package cn.mtianyan.book;

public class BookTest {
    public static void main(String[] args) {
        Book one = new Book("红楼梦","曹雪芹","人民文学出版社",6);
        Book two = new Book("小李飞刀","古龙","中国长安出版社",55.5f);
        one.info();
        System.out.println("===================");
        two.info();
    }
}

使用包进行类管理

文件夹进行文件管理,同一个文件中可以存放多个不同的文件,同名的文件只能存放在不同的文件夹中。

在Java中如何进行不同类文件的管理呢:java中我们通过包来管理java文件,解决同名文件冲突。

Java中一个包里不能存在同名类

域名倒序+模块+功能(域名全部小写)

package cn.mtianyan.book;

包的定义必须放在Java源文件中的第一行,定义类时,我们应该遵循单一职责原则,因此在建立包的时候,建议每个包内存储信息功能单一。

如何实现跨包的类调用?有几种调用形式?我如何告诉编译器我要调用的是哪个包里的Cat呢?

import cn.mtianyan.animal.*; // 加载包下的所有类
import cn.mtianyan.animal.cat; // 加载指定包下的指定类

建议采用 import包名.类名 的方式加载,提高效率

可以直接在程序代码中加载cn.mtianyan.animal.cat

 cn.mtianyan.animal.Cat cat = new cn.mtianyan.animal.Cat();

加载类的顺序跟import导入语句的位置无关,具体包的指定,优先级大于通配符。

import cn.mtianyan.*

import包名.* 只能访问指定包名下的类,无法访问子包下的类

包的作用:

  1. 管理Java文件
  2. 解决同名文件冲突

定义包:

语法: package 包名;例: package cn.mtianyan.animal;

注意: 必须放在Java源文件中的第一行;-个Java源文件中只能有-个package语句;包名全部英文小写;命名方式:域名倒序+模块+功能

导入包语法:

import 包名.类名;

例:导入包中全部类: import cn.mtianyan.*;导入包中指定类: import cn.mtianyan.animal.Cat;

tips(常用系统包):

包名 内容
java.lang 包含Java语言基础的类,该包系统加载时默认导入 如:System、String、Math
java.util 包含Java语言中常用工具 如: Scanner、Random
java.io 包含输入、输出相关功能的类 如: File、InputStream

编程练习

编写自定义类实现用户信息类.

程序参考运行效果图如下:

任务

用户类: 属性(用户名、密码)
用户管理类: 方法(用户信息验证)

package cn.mtianyan.user;

public class User {
    private String username;

    public User(String username, String password) {
        this.setUsername(username);
        this.setPassword(password);
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private String password;
}
package cn.mtianyan.user;

public class UserManage {
    public String checkUser(User one,User two){
        System.out.println("用户名: "+one.getUsername());
        System.out.println("密码: "+one.getPassword());
        System.out.println("用户名: "+two.getUsername());
        System.out.println("密码: "+two.getPassword());
        System.out.println("=================");
        if (one.getUsername().equals(two.getUsername())){
            return  "用户名一致";
        }else {
            return  "用户名不一致";
        }
    }
}
package cn.mtianyan.user;

public class Test {
    public static void main(String[] args) {
        User one = new User("Lucy","123456");
        User two = new User("Mike","123456");
        UserManage userManage = new UserManage();
        System.out.println(userManage.checkUser(one,two));
    }
}

运行结果:

Static关键字(上)

static表示静态信息,它在Java程序中可以和喞些信息組合使用?用它修饰的信息具有哪些特征呢?

public float price; //cat类中
Cat one = new Cat();
one.price = 2000;

Cat two = new Cat();
two.price = 150;

上述代码的结果显然脑补都可以脑补到.但假如我们为价格添上static修饰

public static int price; //售价

添加了static修饰之后,price变成了斜体。

可以看到在main中取值时,提示我们静态的成员变量,应该以一种静态的访问方式访问。此时的运行结果中,两只猫的价格都变成了150。

Static表示的是一个静态的,修饰成员时叫做一个静态成员或类成员,它是属于这个类所有的。无论这个类实例化出多少的对象,它都会共用同一块内存空间。

后赋值的会将前面的内存中的内容覆盖掉; 类对象共享;类加载时产生,销毁时释放,生命周期长

one.price = 2000;
Cat.price = 3000;

既可以通过对象名,也可以通过类名来访问。

Static关键字中

Static 加在属性前面,被称为静态属性,类属性。同理,Static添加到方法前面就从普通方法,变成了类方法。

public static void eat(){
        System.out.println("小猫吃鱼");
}

类方法的调用和我们的静态成员变量的访问时一样的,既可以通过类,也可以通过对象。

        oneCat.eat();
        Cat.eat();

推荐调用方式:类名.静态成员

除了加在属性,方法,static还可以加在哪些地方呢?

不能加载类名前,类名前仅仅允许添加: public abstract final; 因此static + 类 = 不存在的

方法中我们可以定义局部变量,

但是局部变量的前面也是不允许加static的,局部变量仅仅被允许添加final

普通的成员方法是可以访问当前对象的成员变量和方法的。

    public void run(){
        eat(); // 可以调用静态方法
        this.name = "妞妞";
        this.price = 200; // 普通方法也是可以访问静态成员的
    }
    public static void eat(){
        run();
        age = 10;
        price = 200;
    }

可以看到静态方法中是不能访问非静态成员方法和成员属性的,因为它缺少隐含的this参数。

如果非得在静态方法内来访问非静态成员方法呢?

    public static void eat(){
        Cat cat = new Cat();
        cat.run();
        cat.age = 10;
        price = 200;
    }

静态方法中不能直接访问同一个类中的非静态成员,只能直接调用同一个类中的静态成员;非要访问,只能通过对象实例化后,对象.成员方法的方式访问非静态成员。

static关键字(下)

关于代码块的相关知识,java中代码是由大括号括起来的。语句中出现大括号对就叫代码块。

当出现在方法里的时候,叫做普通代码块。普通代码块和一般语句的执行顺序是一样的。

    public void run(){
        {
            System.out.println("我是普通代码块1");
        }
        System.out.println("小猫在跑");
        {
            System.out.println("我是普通代码块2");
        }
    }

普通代码块:顺序执行,先出现, 先执行

当代码块直接在类中定义,与成员方法,属性并列,就被称为是构造代码块。

    {
        System.out.println("我是构造代码块1");
    }// 构造代码块

可以看到,构造代码块比构造函数还先执行。构造代码块:创建对象时调用,优先于构造方法执行。不管构造方法块放在类里面的前后,它都会先于构造函数执行。

多个构造代码块之间有先后顺序,但都先于构造函数执行。

    {
        System.out.println("我是构造代码块1");
    }
    // 中间吧啦吧啦一大堆
    {
        System.out.println("我是构造代码块2");
    }

在构造代码块前面加上static修饰,它就会变成静态代码块。

    {
        System.out.println("我是构造代码块1");
    }
    // 中间吧啦吧啦一大堆
    static {
        System.out.println("我是位于后面的静态代码块");
    }

猜猜谁先执行呢?当时是先有类再有对象。静态的先执行。

        Cat oneCat = new Cat(-1);
        Cat twoCat = new Cat(3);

可以看到,当实例化多个对象的时候,静态代码块只会执行一次,而构造代码块是实例化了多少个对象就执行多少次。

静态代码块: 类加载时调用,优先于构造代码块执行。一个类中也可以有多个静态代码块,它们的执行顺序显然是按照先后顺序了,就不举例子了。无论产生多少类实例,静态代码块只执行一次。

    {
        name = "喵喵";// 非静态
        price = 100; // 静态
        System.out.println("我是构造代码块1");
    }

如图前面所学的一样,构造代码块中可以访问静态 & 非静态成员。静态代码块中只能访问静态成员。

仅希望执行一次的代码可以放在静态代码块中

编程练习

请根据效果图以及任务要求完成代码。

程序参考运行效果图如下:

任务

创建类Code,类中编写构造块、静态代码块以及构造函数; 创建CodeBlock,类中编写的构造块、静态代码块以及构造函数。
在主函数中测试他们的执行的优先顺序

package cn.mtianyan.code;

public class Code {
    {
        System.out.println("Code的构造块");
    }
    public Code(){
        System.out.println("Code的构造方法");
    }
    static {
        System.out.println("Code的静态代码块");
    }
}
package cn.mtianyan.code;

public class CodeBlock {
    {
        System.out.println("CodeBlock的构造块");
    }
    public CodeBlock(){
        System.out.println("CodeBlock的构造方法");
    }
    static {
        System.out.println("CodeBlock的静态代码块");
    }
    
    public static void main(String[] args) {
        CodeBlock codeBlock;
        System.out.println("CodeBlock的主方法");
        System.out.println("产生Code类实例对象");
        Code code = new Code();
        System.out.println("产生CodeBlock实例对象");
        codeBlock = new CodeBlock();
    }
}

运行结果:

static关键字(续)

    public void run(){
        {
            System.out.println("我是普通代码块1");
        }
        System.out.println("小猫在跑");
        {
            System.out.println("我是普通代码块2");
        }
    }

run方法里面有两个代码空间,代码块1和代码块2

一个方法中是不能出现同名变量的,比如run中

    public void run(){
        int temp =10;
        int temp =12;
    }
    public void run(){
//        int temp =10;
        {
            int temp = 11;
            System.out.println("我是普通代码块1");
        }
        System.out.println("小猫在跑");
        {
            int temp = 12;
            System.out.println("我是普通代码块2");
        }
    }

此时是正常的,两个代码块空间中允许有自己的变量值,不会重名造成冲突。但是不能在方法的全局域也就是注释的位置添加同名变量,会与两个变量都造成重复定义的问题。

temp = 14是从它定义一直作用到结束的。

    public void run(){
//        int temp =10;
        {
            int temp = 11;
            System.out.println("我是普通代码块1");
        }
        {
            int temp = 12;
            System.out.println("我是普通代码块2");
        }
        int temp = 13;
        System.out.println("小猫在跑");
    }

编程实践

package cn.mtianyan.ming;

public class Student {
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public  int getAge() {
        return age;
    }

    public  void setAge(int age) {
        Student.age = age;
    }

    public static int age;

}

package cn.mtianyan.ming;

public class StudentTest {
    public static void main(String[] args) {

        Student stu = new Student();
        stu.setName("小红");
        stu.setAge(13);
        Student stu1 = new Student();
        stu1.setName("小明");
        stu1.setAge(18);

        System.out.println(stu.getName()+"今年"+stu.getAge()+"岁了!");
        System.out.println(stu1.getName()+"今天"+stu.getAge()+"岁了!");
    }
}

运行结果:

总结

封装: 通过该类提供的方法来实现对隐藏信息的操作和访问;隐藏对象的信息 & 留出访问的接口

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

合理的封装符合我们的使用习惯: 我们只需知道如何使用,并不需要深究它的内部实现构造。

java中三个步骤实现封装:

修改属性的可见性(设为private);创建getter/setter方法设为public用于属性的读写;在getter/setter方法中加入属性控制语句对属性值的合法性进行判断。

第一步是实现了内部重要信息的隐藏,二三步是留出了对外访问的接口。

包的作用: 1. 管理Java文件 2. 解决同名文件冲突

定义包: 语法 package 包名;

注意: 1. 必须放在Java源文件中的第一行 2. 一个Java源文件中只能有一个package语句 3. 包名全部英文小写4. 命名方式:域名倒序+模块+功能

跨包访问的语法: import 包名.类名;

例:

// 导入包中全部类
import cn.mtianyan.*;

// 导入包中指定类:
import cn.mtianyan.animal.Cat;

通配符只能导入当前mtianyan包下的类,不能导入它的子包如(animal)的类。

static

  1. static+属性 2. static+方法 3. static+类 4. static+方法内局部変量 5. static+代码块

静态属性、类属性 & 静态方法、类方法; 不存在静态类,不存在方法中的静态局部变量;

{}来隔离一个代码块出来,存在于方法当中称之为普通代码块;存在于类当中时被称为构造代码块; 当构造代码块前面加上static关键字的时候就变成了静态代码块。

注意问题:

  1. 静态成员的生命周期: 类加载时产生,销毁时释放,生命周期长
  2. 静态方法中的成员调用:可以直接访问类内的静态成员,不可以直接访问类内的非静态成员。非得访问,只能在方法内实例化对象再通过对象访问。
  3. 各种代码块的执行顺序: 静态代码块只执行一次,构造代码块在每次对象构造的时候调用,普通代码块在调用方法时使用。

在下一集中,我们将通过一个综合案例,带领大家进一步学习“封装”在面向对象编程中的应用。

上一篇下一篇

猜你喜欢

热点阅读