Java基础——面向对象的三大特征

本文详细介绍了Java面向对象编程的三大特性:封装、继承和多态。讲解了如何使用包管理类,类的访问权限,以及getter和setter方法。还探讨了单例设计模式和Lombok库的使用,展示了如何通过UML表示类的关系,并解释了里氏替换原则。此外,文章还讨论了final关键字在类、方法和变量中的应用。
摘要由CSDN通过智能技术生成

1.面向对象的特征-封装

1.1 包

在计算机中保存各种文档的时候,会把不同用途、不同类型的文档按照用户的意愿,分别存放在不同的文件夹中,易于管理和查找。

1.2 包的概念

在复杂的文件系统中,文件的分门别类存储在不同的文件夹中,解决了文件的重名问题,在程序设计过程中,一个系统工程需要编写几百个甚至上千个类文件,也经常遇到类名相同的问题,并且由很多程序研发人员共同协作完成,很难保证不同的程序研发人员选择类名的时候,类名不冲突。Java中使用包管理类文件,Java的类文件存储在不同的包中。在每一个类声明前,使用关键字package定义包。

package com.javatest; 
public class Demo1 {    
    //省略部分代码
}

说明

  1. package只能放在类文件的最上面,com是包,javatest是com的子包。包名将在磁盘上生成对应的物理目录名。就是说Demo1类文件会存放在com文件夹下的javatest文件夹下。

  2. 一个类文件只能有一个包声明语句。

在包中的类的全名是:包名.类名,例如com.javatest.Demo1,称为类的完全限定名。包的命名都是要小写字母。通常每个公司开发的软件系统中包名都是本公司网址的倒置,例如阿帕奇公司的网址是www.apache.org,阿帕奇公司开发的软件中包名通常命名为org.apache开头。

1.3 Java系统包

java官方的JDK中定义了很多的类,称为类库,也称为Java API(应用程序接口),程序员可以直接使用。Java官方将一组功能相关的类组织在一个包中。常用的系统包有:

Ø java.lang:包含了Java语言的核心类,如String、Math、System等。

Ø java.util:包含了大量的工具类,如后面Scanner,Random等。

Ø java.net:包含了一些Java网络编程的相关类。

Ø java.io:包含了一些Java输入/输出操作的相关类。

Ø java.text:包含了一些Java格式化的相关类。

Ø java.sql:包含了Java对数据库操作的相关类。

Ø java.awt和java.swing:包含了Java下的图形界面程序开发的相关类。

1.4 使用其它包中的类

在程序设计中,经常会在一个类中使用另外一个包中的类,此时就需要导入包中的类。有两种使用方法使用其它包中的类

第一种方法是使用import导入类。

import java.util.Scanner;  //导入java.util包中的Scanner类
public class Student  extends Object{
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);  //使用Scanner类
    }
}

第二种方法是书写类的完全限定名。

public class Student  extends Object{   
    public static void main(String[] args) {
        java.util.Scanner input = new java.util.Scanner(System.in);
    }
}

lava.lang包中定义了系统最常用的一些类,该包中的类在使用时无需到如java.lang包,因为java.lang包默认被导入到任何类中。

2. 封装

封装是面向对象的核心特征之一,它提供了一种信息隐藏技术。类的封装包含两层含义,将数据和对数据的操作组合起来构成类,类是一个不可分割的独立单位,类中既要提供与外部联系的接口,同时又要尽可能的隐藏类的实现细节。封装性为软件提供了一种模块化的设计机制,设计者提供标准化的类模块,使用者根据实际需求选择所需的类模块,通过组装模块实现大型软件系统。各模块之间通过接口衔接和协同工作。

类的设计者和使用者考虑问题的角度不同,设计者需要考虑如何定义类中成员变量和方法,如何设置其访问权限问题。类的使用者只需要知道有哪些类可以选择,每个类有哪些功能,每个类中有哪些可以访问的成员变量和成员方法等,而不需要考虑了解其实现的细节。

2.1 类成员的访问权限

按照类的封装性原则,类的设计这既要提供类与外部的联系方式,又要尽可能的隐藏类的实现细节,具体办法就是为类的成员变量和成员方法设置合理的访问权限。

Java为类设置了2种访问权限,为类的成员变量和成员方法设置了4种访问权限。它们是public (共有)、protected(保护)、缺省和private(私有)

(1) public

被public修饰的成员变量和成员方法可以在所有类中访问。所谓在某类中访问某成员变量是指在该类的方法中给该成员变量赋值和取值。所谓在某类中访问成员方法是指在该类的方法中调用该成员方法。所以在所有类的方法中,可以使用被public修饰的成员变量和成员方法。

(2) protected

被protected修饰的成员变量和成员方法可以在声明它的类中访问,在该类的子类中访问,也可以在与该类位于同一个包中的类访问,但不能在位于其它包的非子类中访问。

(3) 缺省

缺省指不使用权限修饰符。不使用权限修饰符修饰的成员变量和成员方法可以在声明它的类中访问,也可以在与该类位于同一个包中的类访问,但不能在位于其它包的类中访问。

(4) private

private修饰的成员变量和成员方法只能在声明它们的类中访问,而不能在其它类(包括子类)中访问。

对4种权限修饰符的总结如下表

内容publicprotected缺省private
同类访问
同包其他类访问×
同包子类访问×
不同包子类访问××
不同包非子类访问×××

讲:public、protected、private访问权限修饰符不能用于方法中声明的变量或形式参数,因为方法中声明的变量或形式参数的作用域仅限于该方法,在方法外是不可见的,在其它类无法访问。

示例1:

在com.javatest包中定义Student类

package com.javatest;
public class Student {
    // 类的属性定义   
    private String name;   // 姓名    
    private int age;      // 年龄 
    private String favourite;  //爱好
    //构造函数  
    public Student() {
    }
    // public修饰的方法定义    
    public void introduce() {
        //name属性、age属性、favourite属性是private修饰的,introduce()方法与这些属性
        //在同一个类内,可以访问   
        System.out.println("大家好,我是"+this.name+",我今年"+this.age+"岁,我的爱好是"+this.favourite);
    }
    //protected修饰的方法    
    protected void study() {        
        System.out.println("我正在学习");
    }
    //默认修饰的方法   
    void dowork(){
        System.out.println("正在做作业");    
    }
}

在com.javatest中定义测试类Test1

package com.javatest;
/**
*提示: Test1类于Student类位于同一个包中 
* 
*/
public class Test1 {    
    public static void main(String[] args) {
        Student s =new Student();
        s.name="林冲";    //name属性是private修饰的,此处不允许访问 
        s.age=22;      //age属性是private修饰的,此处不允许访问   
        s.favourite="篮球";  //favourite属性是private修饰的,此处不允许访问 
        s.introduce();     //introduce()方法是public修饰的,此处可以访问     
        s.study();       //study()方法是protected修饰的,此处可以访问    
        s.dowork();      //dowork()方法是默认修饰的,此处可以访问  
    }
}

在com.xinbing包中定义测试类Test2

package com.xinbing;
import com.javatest.Student;
/* 
*提示: Test2类于Student类不在同一个包中 
*
*/
public class Test2 {
    public static void main(String[] args) {
        Student s =new Student();   
        s.name="林冲";    //name属性是private修饰的,此处不允许访问 
        s.age=22;      //age属性是private修饰的,此处不允许访问   
        s.favourite="篮球";  //favourite属性是private修饰的,此处不允许访问 
        s.introduce();     //introduce()方法是public修饰的,此处可以访问 
        s.study();       //study()方法是protected修饰的,此处不可以访问       
        s.dowork();      //dowork()方法是默认修饰的,此处不可以访问
    }
}

2.2 getter/setter访问器

上例中com.javatest.Student类中的name、age、favourite都声明为private,即只有在类内才能访问这个三个属性。而在com.itxinbing.Test2类中需要为Student对象s的name、age、favourite属性就无法做到了。

在java中通常都会将属性私有化,然后提供一对public的getter方法和setter方法对私有属性进行访问。getter方法和setter方法称为属性访问器。

示例2:

重构com.javatest.Student类,为该类提供getter和setter访问器。

package com.javatest;
public class Student {
    // 类的属性定义
    private String name;   // 姓名    
    private int age;     // 年龄  
    private String favourite; //爱好
    //属性访问器 
    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 getFavourite() {      
        return favourite;
    }   
    public void setFavourite(String favourite) {        
        this.favourite = favourite; 
    }   
    //省略部分代码
}

重构com.xinbing.Test2类,在该类中创建com.javatest.Student对象s,让s访问公有getter和setter访问器为name、age、favourite属性赋值。

 

2.3 类的访问权限

声明一个类只能使用public和缺省的权限访问修饰符两种,不能使用protected和private。虽然一个java源文件可以定义多个类但只能有一个类能够使用public修饰符,该类的类名与类文件的文件名必须相同。

问:我在一个java源文件中定义了两个public修饰的类,为什么报错呢? public class Exec1 {} public class Exec2{}
答:一个java源文件可以定义多个类但只能有一个类能够使用public修饰符。
问:我在一个Hello.java源文件中定义了public修饰的World类,为什么报错呢? public class World {}
答:public修饰的类的类名必须与类文件的文件名必须相同

3. static

static是一个修饰符,用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能。

3.1 static修饰成员方法

static修饰的方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都必须依赖具体的对象才能够被调用。

但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。例如:

 

在上面的代码中,由于print2方法是独立于对象存在的,可以直接用过类名调用。假如说可以在静态方法中访问非静态方法/变量的话,那么如果在main方法中有下面一条语句:

  MyObject.print2();

  此时对象都没有,str2根本就不存在,所以就会产生矛盾了。同样对于方法也是一样,由于你无法预知在print1方法中是否访问了非静态成员变量,所以也禁止在静态成员方法中访问非静态成员方法。

  而对于非静态成员方法,它访问静态成员方法/变量显然是毫无限制的。

  因此,如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

3.2 static变量

  static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本【存放在方法区】,它当且仅当在类初次加载时会被初始化【加final和不加final的static变量初始化的位置不一样】。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

  static成员变量的初始化顺序按照定义的顺序进行初始化。

4.代码块

4.1 什么是代码块

代码块又称为初始化块,属于类中的成员(即是类的一部分),类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同,没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或者创建对象时隐式调用。

4.2 基本语法

[修饰符]{

代码

};

注意:

  1. 修饰符可选,要写的话,也只能些static或synchronized(此处不讲)。

  2. 代码块分为两类:

    第一类:使用static修饰的叫做静态代码块

    第二类:没有用static修饰的叫做普通代码块

  3. 逻辑语句可以分为任何逻辑语句(如:输入,输出,方法调用,循环,判断等)

  4. ";"号可以写上也可以不写。

4.3 普通代码块与静态代码块

使用static修饰的叫做静态代码块 没有用static修饰的叫做普通代码块

 静态代码块

static代码块也叫静态代码块,作用是对类进行初始化,而且他随着类的加载执行,且只会执行一次,如果是普通代码块,每创建一个对象就会执行一次。

类什么时候会被加载?? 1) 创建实例对象时(new) 2) 使用类的静态成员(静态属性,静态方法)

注意: 在一个类被加载时,该类的静态代码块一定会执行。 static代码块在类加载时被执行,且只会执行一次。 普通代码块:只有在创建对象实例(构造器被执行)时,会被隐式调用,被创建一次就调用一次。

类被加载的案例:

 public class CodeBlock02 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 当创建一个类的实例对象时,类被加载
//        BB aa = new BB();
      
        // 2. 使用类的静态成员时(静态属性,静态方法)类被加载
//        System.out.println(Cat.n1);
 //static在类加载时被执行,且只执行一次
    Cat cat1 = new Cat();
    Cat cat2 = new Cat();
    }
}
class BB{
    static {
        System.out.println("BB的静态代码块执行了......");
    }
}

class Cat{
    public static int n1 = 999; //静态属性
    static {
        System.out.println("Cat的静态代码块执行了......");
    }
}

总结:

  1. static代码块时是在类加载时执行,且只会执行一次。

  2. 普通代码块实在创建对象时调用的(构造器执行),且每创建一个对象,执行一次。

创建一个对象时,类中代码的调用顺序

先 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用) 后 调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化的调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用) 最后 调用构造器 实例:

public class CodeBlock03 {
    public static void main(String[] args) {
        A a = new A();
    }
}
class A{
    {
        System.out.println("A 普通代码块执行了");
    }
    private int n2 = getN2();
    private static int n1 = getN1();
    public A(){
        System.out.println("空参构造被执行了");
    }
    static {
    System.out.println("A 静态代码块");
	}
    private static int getN1() {
        System.out.println("getN1被调用了");
        return 100;
    }
    private int getN2() {
        System.out.println("getN2被调用了");
        return 200;
    }
}

5. 单例设计模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。

  • 2、单例类必须自己创建自己的唯一实例。

  • 3、单例类必须给所有其他对象提供这一实例。

5.1 介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例:

  • 1、一个班级只有一个班主任。

  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。

  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。

  • 2、避免对资源的多重占用(比如写文件操作)。

使用场景:

  • 1、要求生产唯一序列号。

  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。

  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

5.2 单例模式的几种实现方式

单例模式的实现有多种方式,先介绍如下两种:

饿汉式

这种方式比较常用,但容易产生垃圾对象。

实例

public class Singleton { 
    //创建 SingleObject 的一个对象
    private static Singleton instance = new Singleton(); 
    //让构造函数为 private,这样该类就不会被实例化
    private Singleton (){}  
    //获取唯一可用的对象
    public static Singleton getInstance() {  
        return instance;
    }
}

优点:没有加锁,执行效率会提高。 缺点:类加载时就初始化,浪费内存。 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

懒汉式

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

实例

public class Singleton {  
    private static Singleton instance; 
    private Singleton (){}   
    public static Singleton getInstance() { 
        if (instance == null) {     
            instance = new Singleton(); 
        }
        return instance;
    }
}

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

对上述懒汉式进行改进

实例

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

对getInstance()方法使用 synchronized 保证单例,但加锁会影响效率。 getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。 优点:第一次调用才初始化,避免内存浪费。

Java中还有其他实现单例的方式,我们后续学习过程给大家介绍。

6.lombok

6.1 Lombok简介

官方介绍 Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

大概的意思:Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。官网链接

Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。

Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString等方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。

使用Lombok的原因:

根据Lombok的使用规则,如果想要使用Lombok就需要删除在项目中(JavaBean)存在的已经生成的getter方法setter方法以及equals和hash方法,当然Lombok也提供了对打印日志的处理。这样在使用Lombok以后就会大大减少项目中的代码量,同时由于Lombok有自动修改的功能,这也提供了项目中代码的执行效率。

综上所述,使用Lombok的两点主要原因就是:

(1)、简化冗余的JavaBean代码。

(2)、大大提高JavaBean中方法的执行效率。

6.2 Lombok使用

安装插件

打开idea的设置,点击Plugins,点击Browse repositories,在弹出的窗口中搜索lombok,然后安装即可。

 

项目导入

Java项目中导入Jar包的方式有两种,分别为maven导入(后面讲)和手动导入。

手动导入jar包

  1. 下载jar包到本地,下载地址:

  2. 导入项目

  3. 导入后是可以直接使用的,但编译会报错。

idea支持注解的选项

idea创建的项目默认是不支持注解的,需要手动勾选支持注解的选项

File >> setting >> 搜索Compiler >> 点击 Annotation processors >> 勾选 Enable processing >> 点击OK(下面有具体的图形操作流程)

 打开idea的setting,找到下图的选项,加上:-Djps.track.ap.dependencies=false

 

设置完之后,重启IDEA。

Lombok常用注解

  • @Data注解:在JavaBean或类JavaBean中使用,这个注解包含范围最广,它包含getter、setter、NoArgsConstructor注解,即当使用当前注解时,会自动生成包含的所有方法;

  • @getter注解:在JavaBean或类JavaBean中使用,使用此注解会生成对应的getter方法;

  • @setter注解:在JavaBean或类JavaBean中使用,使用此注解会生成对应的setter方法;

  • @NoArgsConstructor注解:在JavaBean或类JavaBean中使用,使用此注解会生成对应的无参构造方法;

  • @AllArgsConstructor注解:在JavaBean或类JavaBean中使用,使用此注解会生成对应的有参构造方法;

  • @ToString注解:在JavaBean或类JavaBean中使用,使用此注解会自动重写对应的toStirng方法;

  • @EqualsAndHashCode注解:在JavaBean或类JavaBean中使用,使用此注解会自动重写对应的equals方法和hashCode方法;

  • @Slf4j:在需要打印日志的类中使用,当项目中使用了slf4j打印日志框架时使用该注解,会简化日志的打印流程,只需调用info方法即可;

  • @Log4j:在需要打印日志的类中使用,当项目中使用了log4j打印日志框架时使用该注解,会简化日志的打印流程,只需调用info方法即可;

在使用以上注解需要处理参数时,处理方法如下(以@ToString注解为例,其他注解同@ToString注解):

@ToString(exclude="column")

意义:排除column列所对应的元素,即在生成toString方法时不包含column参数;

@ToString(exclude={"column1","column2"})

意义:排除多个column列所对应的元素,其中间用英文状态下的逗号进行分割,即在生成toString方法时不包含多个column参数;

@ToString(of="column")

意义:只生成包含column列所对应的元素的参数的toString方法,即在生成toString方法时只包含column参数;;

@ToString(of={"column1","column2"})

意义:只生成包含多个column列所对应的元素的参数的toString方法,其中间用英文状态下的逗号进行分割,即在生成toString方法时只包含多个column参数; 示例代码

不使用Lombok(传统方法)

package com.user;
 
public class UserInfo {
 
    private String userid;
    private String username;
    private String userpass;
    private String userphone;
    private String useremail;
    private String usercard;
    private String useraddress;
 
    @Override
    public String toString() {
        return "UserInfo{" +
                "userid='" + userid + '\'' +
                ", username='" + username + '\'' +
                ", userpass='" + userpass + '\'' +
                ", userphone='" + userphone + '\'' +
                ", useremail='" + useremail + '\'' +
                ", usercard='" + usercard + '\'' +
                ", useraddress='" + useraddress + '\'' +
                '}';
    }
 
    public String getUserid() {
        return userid;
    }
 
    public void setUserid(String userid) {
        this.userid = userid;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getUserpass() {
        return userpass;
    }
 
    public void setUserpass(String userpass) {
        this.userpass = userpass;
    }
 
    public String getUserphone() {
        return userphone;
    }
 
    public void setUserphone(String userphone) {
        this.userphone = userphone;
    }
 
    public String getUseremail() {
        return useremail;
    }
 
    public void setUseremail(String useremail) {
        this.useremail = useremail;
    }
 
    public String getUsercard() {
        return usercard;
    }
 
    public void setUsercard(String usercard) {
        this.usercard = usercard;
    }
 
    public String getUseraddress() {
        return useraddress;
    }
 
    public void setUseraddress(String useraddress) {
        this.useraddress = useraddress;
    }
 
}
使用Lombok

package com.user;
 
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
 
@Setter
@Getter
public class UserInfo {
 
    private String userid;
    private String username;
    private String userpass;
    private String userphone;
    private String useremail;
    private String usercard;
    private String useraddress;
 
}
只给单个参数添加

package com.user;
 
import lombok.Getter;
import lombok.Setter;
 
public class UserInfo {
 
    @Getter@Setter private String userid;
    private String username;
    private String userpass;
    private String userphone;
    private String useremail;
    private String usercard;
    private String useraddress;
 
}

7. 类与类之间的关系

7.1 用UML表示一个类

类图一般是三行的结构:

类名称 属性:格式(可见性 名称:类型[=缺省值]) 方法:格式(可见性 名称(参数列表)[返回类型])

7.2 StarUML简介

StarUML(简称SU),是一种创建UML类图,生成类图和其他类型的统一建模语言(UML)图表的工具。StarUML是一个开源项目之一发展快、灵活、可扩展性强(zj)。

特点

  • 可绘制9款UML图:用例图、类图、序列图、状态图活动图、通信图、构件图、部署图以及复合结构图等。

  • 完全免费:StarUML是一套开放源码的软件,不仅免费自由下载,连代码都免费开放。

7.3 StarUML使用

  1. 新建“画布”

    1. 添加新工程

    启动StarUML,然后一个名叫“New Project By Approach”的对话框会弹出,选择“Empty Project”,在右边的“Model Explorer”面板中可以看到新建的“Untitled”工程,工程的属性可以在下方的Properties面板中修改(工程名、作者等)。

    1. 添加模型

    通过“Model”主菜单,或者在Model Explorer面板的工程上右击,依次” Add — Model ”

    1. 添加类图

    通过“Model”主菜单,或右击选定模型,依次“Add Diagram — Class Diagram”。

    1. 设置profile(UML轮廓)

    通过“Model — Profile…”菜单去设置工程所需的profile。这决定了工程所使用的规则和约定。一定要包含”JAVA Porfile”这一项目。

    profile manager

    1. 保存工程

    立即就保存工程,这样在出现问题的时候,您就不会丢失信息。

    从“File ”菜单,选择“Save” ,并选择一个地方以保存工程。你的StarUML项目现在应该看起来的是这样的:

    a StarUML project appearence

  2. 开始“作画”(How)

    1.创造图表

    现在,开始真正创造图表,从默认就在屏幕的左边的“Toolbox”面板选择“类”图标,然后左键单击diagram窗口的某处。这样就使用通用名字创造了一个新的类。双击,将类改名为Circle。

    2.添加属性

    右击图中的目标,在弹出菜单中选择“Add”中的“Attribute”(被标示为绿色),为其添加一个属性(或者域),填入期望的名字“_radius”。

类与类之间主要有6种关系,包括依赖,关联,聚合,组合,继承,实现。他们的耦合度是一次增强的。

7.4 依赖(Dependency)

依赖主要体现了一种使用关系,两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,主要体现为依赖关系。

依赖关系主要有3种表现形式:(以类A中使用了类B为例)

类B是作为类A的方法参数 类B是类A的方法中的局部变量 在类A中调用类B的静态方法

在依赖中使用的是“虚线+箭头”。

虚线+箭头表示

例如:人与车

 

public class Perple{
    public void work(Car car){
        System.out.println("上班乘坐的交通工具是" + car.getName);
    }
}

7.5 关联(Association)

关联主要体现的是一种对应关系,两个相对独立的对象,当一个对象的实例与另一个对象的实例存在固定的对应关系时,这两个对象之间为关联关系。通常是将一个类的对象作为另一个类的成员变量。

关联关系的分类:

单向关联 双向关联

(单向关联)

实线+箭头表示

 

例如:老师与课程

public class Teacher{
    private String name;
    private Course course;
​
    public Teacher(String name,Course course){
        this.name= name;
        this.course= course;
    }
}

7.6 聚合(Aggregation)

聚合是关联关系的一种,表现了一种弱的“拥有”关系,关联关系的对象之间是相互独立的,但是聚合关系的对象之间存在着一种包容关系,体现的是A对象可以包容B对象,但是B对象不是A对象的一部分。他们之间是“整体-个体”的关系。

成员对象通常作为构造方法、Setter方法或业务方法的参数注入到整体对象中。

空心菱形+箭头表示

例如:大雁和雁群

 

7.7 组合(Composition)

组合是一种强的“拥有”关系,组合中的类之间是“整体-部分”的关系,“整体”负责“部分”的生命周期,“部分”和“整体”的生命周期是一样的,“部分”单独存在是没有任何意义的。通常在整体类的构造方法中直接实例化成员类。

实心菱形+箭头表示

例如:人和手、腿、头

 

8.继承

继承性是面向对象的核心特征之一,继承是由已有类创建新类的机制,以简化新类的设计,达到代码复用的目的,是实现多态的基础。利用继承机制,可以先创建一个具有共性的一般类,根据该一般类再创建具有特殊性的新类,新类继承一般类的属性和方法,并根据需要增加它自己的新属性和方法。类的继承机制是面向对象程序设计中实现软件可重用性的重要手段。

由已有类定义新类,已有类称为父类或超类,新类称为子类或派生类。通过继承,子类拥有父类所有的成员变量和成员方法,子类中既可以定义新的属性和方法,也可以对父类中的成员变量和成员方法进行更改,使类的功能得以扩充。

8.1 继承的实现

子类的声明

声明子类的形式如下

[修饰符] class 子类名 extends 父类名{

子类成员变量

子类成员方法

}

8.2 继承的原则

  1. Java是单继承的,一个类只能继承一个父类。

  2. 子类继承父类的实例变量和类变量(静态变量)

  3. 子类继承父类除了构造方法以外的实例成员和类方法(静态方法)

  4. 子类不能继承父类的构造方法,因为父类的构造方法用来创建父类对象,子类需要声明自己的构造方法,用来创建子类自己的对象。

  5. 子类可以重新声明父类的成员。

8.3 子类对父类成员的访问权限

子类虽然继承了父类的成员变量和成员方法,但并不是对父类所有成员变量和成员方法都具有访问权限,即并不是在自己声明的方法中能够访问父类所有成员变量或成员方法。Java中子类访问父类成员的权限如下:

  1. 子类对父类的private成员没有访问权限。子类方法中不能直接引用父类的private成员变量,不能直接调用父类的private成员方法,但可以通过父类非private成员方法访问父类的成员变量。

  2. 子类对父类的public和protected成员具有访问权限。

  3. 子类对父类的缺省权限成员的访问权限分为两种情况:对于同一个包中的父类缺省权限成员具有访问权限,对其它包中父类的缺省权限成员没有访问权限。

类中成员的访问权限体现了类封装的信息隐藏原则:如果类中成员仅限于该类自己使用,则声明为private;如果类中成员允许子类使用,则声明为protected;如果类中成员允许所有类访问,则声明为public。

下面通过一个案例来理解继承。动物有很多种类,如有猫类,狗类,鸭子类。猫类有很多品种,如“波斯猫”、“家猫”。狗类有很多品种,如“牧羊犬”,“藏獒”。鸭子有很多品种,如“野鸭”,“家鸭”。所有的动物动能发出叫声。扈三娘打算开一个宠物店(PetShop),在宠物店中卖不同品种的猫、狗、鸭子。扈三娘给每一个宠物都起了名字,当客人来购买宠物时,宠物需要自我介绍,以赢得准主人的欢心。

根据上面的描述,可以分析出动物(Animal)是父类,猫(Cat)、狗(Dog)、鸭子(Duck)是子类。我们知道父类中定义的是所有子类共性的属性和方法,因此在父类中定义动物的类别(type)、品种(breed)、名称(name)属性和叫声(sound)方法、自我介绍(introduce)方法。

任务1:扈三娘开宠物店
定义Animal父类,定义Cat、Dog、Duck子类,定义PetShop宠物店类
package com.javatest;
/** 
*动物类(父类) 
* 
*/
public class Animal {
    //私有属性  
    private String type;    
    private String breed;   
    private String name;
    //构造函数
    public Animal(String type,String breed,String name){        
        this.type = type;   
        this.breed =breed;
        this.name = name;   }   
    //自我介绍方法
    public void introduce(){
        System.out.println("主人好,我是"+this.type+",我的品种是"+this.breed+",我的名字叫"+this.name);
    }   
    //叫声方法  
    public void sound(){
    }
}
package com.javatest;
/* 
* 猫类(子类) 
* */
public class Cat extends Animal{    
    //子类构造函数
    public Cat( String breed, String name) {
        //调用父类构造函数
        super("猫", breed, name);
    }
}
package com.javatest;
/*
* 狗类(子类) 
* */
public class Dog extends Animal {
    //子类构造函数
    public Dog(String breed, String name) { 
        //调用父类构造函数  
        super("狗", breed, name);    
    }
}
package com.javatest;
/*
* 鸭子类(子类) 
* */
public class Duck extends Animal {
    //子类构造函数    
    public Duck(String breed, String name) {
        //调用父类构造函数
        super("鸭子", breed, name);   
    }
}
package com.javatest;
/* 
* 宠物店(测试类) 
* */
public class PetShop {
    public static void main(String[] args) {
        Cat cat = new Cat("波斯猫", "大花");  //定义猫对象cat
        Dog dog = new Dog("牧羊犬","大黑"); //定义狗对象dog   
        Duck duck = new Duck("野鸭","大鸭");//定义鸭子对象duck
        cat.introduce();  //猫调用自我介绍方法   
        dog.introduce(); //狗调用自我介绍方法    
        duck.introduce();//鸭子调用自我介绍方法
    }
}

运行结果

主人好,我是猫,我的品种是波斯猫,我的名字叫大花

主人好,我是狗,我的品种是牧羊犬,我的名字叫大黑

主人好,我是鸭子,我的品种是野鸭,我的名字叫大鸭

说明:

  1. main方法中的猫类对象cat、狗类对象dog、鸭子类对象duck都可以调用introduce()方法,但Cat类、Dog、类、Duck类中并未定义introduce()方法,这是因为Cat类、Dog类、Duck类从父类Animal类中继承了introduce()方法。

  2. super是java的关键字表示调用父类成员,this是java的关键字表示调用当前类的成员。

8.4 this和super

子类可以使用super引用父类的成员变量、成员方法及其构造方法。

  1. 引用父类成员变量

子类自动继承父类所有的成员变量,可以使用以下方式引用父类的成员变量。形式如下:

super.成员变量名;

当子类中没有声明与父类同名的成员变量时,引用父类的成员变量可以不使用super,例如:

class Father{
    String name;//父类中定义的name属性
}
​
class Son extends Father {
    public Son(){
        name = "父类"; //省略super,调用父类name属性
    }
}

但当子类中声明了与父类同名的成员变量时,为了引用父类的成员变量,必须是super,否则引用的是子类中的同名成员变量,例如:

class Father{
    String name;  //父类中定义的name属性
}
​
class Son extends Father {
    String name;  //子类中定义的name属性
    public Son(){
        super.name = "父类"; //使用super,调用父类name属性
        name="子类";    //省略super,调用子类name属性
    }
}
  1. 调用父类成员方法

子类自动继承父类所有的成员方法,可以使用以下方式调用父类的成员方法。形式如下:

super.成员方法(参数列表);

当子类中没有声明与父类同名的且参数列表也相同的成员方法时,调用父类的成员方法可以不使用super;

class Father{
    //父类定义了say()方法
    public void say(){
​
    }
}
​
class Son extends Father {
    public void introduce() {
        say();//子类没有定义say()方法,省略super时调用的是父类的say()方法
    }
}

当子类中声明了与父类同名的且参数列表也相同的成员方法时,为了调用父类的成员方法,必须使用super,否则调用的是子类中的同名且参数列表也相同的方法,例如:

class Father {
    // 父类定义了say()方法
    public void say() {
    }
}
​
class Son extends Father {
    //子类定义了与父类同名,同参的方法
    public void say() {
    
    }

    public void introduce() {
        say();// 省略super,调用的是子类的say()方法
        super.say();//使用super,调用父类的say()方法
    }
}
  1. 调用父类构造方法

在子类构造方法中,可以通过super调用父类的构造方法,其调用的形式如下:

super(参数列表);

此处的参数列表由父类构造方法的参数列表决定,并且super()必须是子类构造方法体中的首条语句。

可以使用this引用当前对象的成员变量、成员方法和构造方法。

  1. 访问成员变量,形式如下:

this.成员变量名;

当成员方法中没有与成员变量同名的参数时,this可以省略,例如:

class Demo  {
​
    String name;
​
    public void setName(String myname) {
        //方法参数myname与成员变量没有相同的名称,可以省略this
        name=myname;
    }
}

但当成员方法中存在与成员变量同名参数时,引用成员变量时其名前的this不能省略,因为成员方法中缺省的是引用方法中的参数,例如

class Demo  {
    String name;

    public void setName(String name) {
        //方法参数name与成员变量name名称相同,
        //使用this调用的是成员变量,不使用this调用的是方法参数
        this.name=name;
    }
}
  1. 调用成员方法,形式如下:

this.成员方法名(参数列表);

  1. 调用构造方法。在构造方法中,可以通过this调用本类中具有不同参数列表的构造方法。形式如下:

this(参数列表);

class Dog  {
        String name;
        public Dog(){ //使用this调用本类中具有不同参数的构造方法
            this("未知");
        }
​
        public Dog(String name){
            this.name = name;
        }
}

8.5 继承中的构造函数调用次序

实例化子类对象时,先调用子类构造函数,然后调用父类构造函数。父类构造函数为属性初始化后,子类再为子类属性初始化。

8.6 继承中的实例化

实例化子类对象时,先实例化父类对象,后实例化子类对象。也就是说实例化子类对象时,父类对象也被实例化了。

8.7 Object类

在Java中,java.lang.Object类是所有类的父类,当一个类没有使用extends关键字显式继承其他类的时候,该类默认继承了Object类,因此所有类都是Object类的子类,都具有Object类的方法和属性。Object类的成员如下表:

序号方法签名
1public final native Class<?> getClass();
2public native int hashCode();
3public boolean equals(Object obj)
4protected native Object clone() throws CloneNotSupportedException;
5public String toString()
6public final native void notify();
7public final native void notifyAll();
8public final native void wait(long timeout) throws InterruptedException;
9public final void wait(long timeout, int nanos) throws InterruptedException
10public final void wait() throws InterruptedException
11protected void finalize() throws Throwable { }

表 Object类的方法

9.多态

多态是面向对象的核心特征之一。多态性是指同一名称的方法可以有多种实现(方法实现是指方法体)。系统根据调用方法的参数或调用方法的对象自动选择一个方法执行。多态简单的理解就是多种形态、多种形式。多态可以通过方法重载(overload)和方法重写(override)来实现。

多态就是在继承关系中,子类重写父类的方法,以达到同一个方法在不同的子类中有不同的实现。

9.1 方法重写override

方法重写是指子类重新定义了父类同名的方法,方法重写也叫方法覆盖。重写表现为父类与子类之间方法的多态性。如果父类中的方法体不适合子类,子类可以重新定义。子类中定义的方法与父类中的方法具有相同的参数列表,但具有不同的方法体。父类与子类具有相同的方法名称和参数列表称为子类重写了父类的方法。

在任务一的宠物店中Animal类的sound()方法是空方法,方法内部没有写任何代码。这是因为不同的宠物叫声不同,所以在Animal类的sound()方法中不能发出具体的叫声。例如如果Animal类的sound()方法发出狗的叫声,那么Animal被猫继承后,猫就发出了狗的叫声,这是不合理的,因此Animal类的sound()方法是空方法。

Animal类的sound()方法是空方法,显然不能满足Cat类、Dog类、Duck类的需要,因为Dog类继承Animal类的空方法sound()后,在调用sound()方法时,没有发出声音。也就是说父类的sound()方法不能满足子类的需要,此时子类可以对父类的方法进行重写。

任务3:让宠物能够发出叫声
在Cat类、Dog类、Duck类中重写父类Animal的sound()方法,让猫、狗、鸭子能够发出自己的叫声。
public class Cat extends Animal {
    //此处省略部分代码  
    @Override
    public void sound() {
        System.out.println("猫的叫声喵喵喵");  
    }
}
​
public class Dog extends Animal {
    //此处省略部分代码  
    @Override
    public void sound() {
        System.out.println("狗的叫声汪汪汪");
    }
}
public class Duck extends Animal {
    //此处省略部分代码  
    @Override
    public void sound() {
        System.out.println("鸭子的叫声嘎嘎嘎"); 
    }
}
public class PetShop{
    public static void main(String[] args) {
        Cat cat = new Cat("波斯猫", "大花");
        Dog dog = new Dog("牧羊犬","大黑");
        Duck duck = new Duck("野鸭","大鸭");
        Animal animals[] =new Animal[]{cat,dog,duck};
        int index =new java.util.Random().nextInt(animals.length);  
        Animal animal = animals[index]; //父类引用指向子类对象
        animal.introduce(); //父类引用调用子类对象的introduce()方法  
        animal.sound(); //父类引用调用子类对象的sound()方法  
    }
}

运行结果

猫的叫声喵喵喵

问:在sound()方法上标注了一个@Override,这是什么呢?
答:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。所有的注解都以@开头。@Override是java中用于标注方法重写的注解。

9.2 里氏替换原则

上例中PetShop类的main方法的最后一行代码animal.sound();就是一个典型的多态的表现。该行代码是父类引用指向子类对象。

里氏替换原则(Liskov Substitution Principle LSP)是面向对象设计的基本原则之一。里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现,也就是说凡是可以使用父类的地方,都可以使用子类代替。里氏替换原则实际上就是父类引用(引用类型的变量也称为引用)指向子类对象,父类引用指向子类对象是指父类的变量名指向到了子类的实例上。在继承中子类的功能比父类的功能强,因为子类除了继承父类的所有方法外,子类还可以添加自己的方法。如果将类功能的多少用矩形大小框来表示,那么子类就是大框,父类就是小框。如下图所示,父类引用animal原本应该指向小框的父类对象,但父类引用animal确指向了大框的子类对象Dog。

 

图 父类引用指向子类对象

我们再来看上例中PetShop的代码。

public static void main(String[] args) {
    Cat cat = new Cat("波斯猫", "大花"); 
    Dog dog = new Dog("牧羊犬","大黑"); 
    Duck duck = new Duck("野鸭","大鸭");
    Animal animals[] =new Animal[]{
        cat,
        dog,
        duck
    };
    int index =new java.util.Random().nextInt(animals.length);
    Animal animal = animals[index];
    animal.introduce(); //父类引用调用子类对象的introduce()方法
    animal.sound(); //父类引用调用子类对象的sound()方法
}
  1. 第2、3、4行定义了子类对象cat、dog、duck,第5行定义了父类的数组animals,并将cat、dog、duck作为数组animals的元素,即父类的数组中存储的是子类对象。

  2. 第11行是将父类数组中的一个元素(其实是子类对象)赋值给父类引用animal,这种赋值方式通常说成父类引用指向子类对象。

  3. 当父类引用指向子类对象时,父类引用所调用的方法是子类的方法。因此第13行父类引用animal调用的是子类的sound()方法。

  4. 第10行的index值是随机的,因此第11行的父类引用animal可能指向任何一个子类对象,这个父类引用指向哪个子类对象,第13行就调用哪个子类的sound()方法,不同子类的sound()方法发出不同的声音,表现出行为的多态性。

  5. 如上图中,父类引用animal原本应该指向地址编号为0xFF01的Animal对象,本例中指向了地址编号为0xEE01的Dog对象,因此animal.sound()调用的就是Dog对象的sound()方法。

再来看一个里氏替换的例子,在宠物店中定义buy方法,buy方法的参数是Animal类型,调用该方法时可以传入Animal类型,也可以传入Animal的子类类型。当传入子类类型时,就是用子类对象替换父类对象。在buy方法内部使用父类引用animal调用introduce()方法和调用sound()方法时,传入那个子类对象,就调用子类对象的introduce()方法和sound()方法。

public class PetShop {  
    public void buy(Animal animal){
        animal.introduce();
        animal.sound();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("波斯猫", "大花");  
        Dog dog = new Dog("牧羊犬","大黑");  
        Duck duck = new Duck("野鸭","大鸭");    
        Animal animals[] =new Animal[]{
            cat,
            dog,    
            duck    
        };
        int index =new java.util.Random().nextInt(animals.length);
        new PetShop().buy(animals[index]); //里氏替换
    }
}

10.final

java中有一个非常重要的关键字final,用它可以修饰类及其类中的成员变量和成员方法。用final修饰的类不能被继承,用final修饰的成员方法不能被重写,用final修饰的成员变量不允许被修改

10.1 final类

有时候,出于安全考虑,有些类不允许继承。有些类定义的已经很完美,不需要再生成子类。凡是不允许继承的类需要声明为final类。

final class A{
​
}
class B extends A{ 
//此处错误,A类是final类,不允许继承。
}

Java系统类String、Byte、Short、Integer、Double等类都是final类。

10.2 final方法

出于安全考虑有些方法不允许被子类重写,不允许被子类重写的方法需要声明为final方法。

class A{    
    public final void method(){ 
    }
}
class B extends A{
    //此处错误,method方法在父类中定义为final方法,不允许被子类重写
    public void method(){
    }
}

10.3 final变量

final修饰的变量叫常量,其值不允许修改。

class A{    
    final int i = 23;
    public static void main(String[] args) {
        A a =new A();       
        a.i =20;//此处错误,i是常量,不允许修改   
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值