Review: Java SE 02(类和对象、方法和封装、static关键字和继承、多态和特殊类、特殊类)

Java SE 02

一、类和对象

1. 面向对象思想

  • 面向对象指以属性和行为的观点去分析现实生活中的事物
  • 面向对象的思想精髓(封装、继承、多态)

2. 类和对象及引用

  1. 类和对象概念

    • 类:就是“分类”,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性的成员变量以及描述行为的成员方法
    • 对象:主要指现实生活中客观存在的实体,在Java语言中对象体现为内存空间中的一块存储区域
    • 关系:类是用于构建对象的模板,对象的数据结构由定义它的类来决定
  2. 类的定义

    class 类名 {
    	类体;
    }
    

    通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写

  3. 成员变量的定义

    class 类名 {
    	数据类型 成员变量名 = 初始值; 
    }
    

    当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首字母大写

  4. 对象的创建

    当一个类定义完毕后,可以使用new关键字来创建该类的对象,这个过程叫做类的实例化

    new 类名();
    

    创建对象的本质就是在内存空间的堆区申请一块存储区域, 用于存放该对象独有特征信息

  5. 成员变量的初始值

    对象创建后,其成员变量可以按照默认的方式初始化

  6. 注意事项

    • 引用类型变量用于存放对象的地址,可以给引用类型赋值为null,表示不指向任何对象
    • 当某个引用类型变量为null时无法对对象实施访问(因为它没有指向任何对象)。此时,如果通过引用访问成员变量或调用方法,会产生NullPointerException 异常

3. 成员方法

  1. 成员方法的定义

    class 类名 {
    	返回值类型 成员方法名(形参列表) {
    		成员方法体; 
        } 
    }
    

    当成员方法名由多个单词组成时,要求从第二个单词起每个单词的首字母大写

  2. 返回值类型

    • 返回值类型主要指返回值的数据类型,可以是基本数据类型,也可以是引用数据类型
    • 在方法体中使用return关键字可以返回具体的数据内容并结束当前方法
    • 当该方法不需要返回任何数据内容时,则返回值类型写void即可
  3. 形参列表

    • 形式参数列表主要指多个形式参数组成的列表,语法格式如下:

      数据类型 形参变量名1, 数据类型 形参变量名2, ...

    • 若该方法不需要带入任何数据内容时,则形参列表位置留空即可

  4. 方法的调用

    • 格式:引用变量名.成员方法名(实参列表);
    • 实际参数列表主要用于对形式参数列表进行初始化操作,因此参数的个数、类型以及顺序都要完全一致
    • 实际参数可以传递直接量、变量、表达式、方法的调用等
  5. 可变长参数

    • 格式:返回值类型 方法名(参数的类型... 参数名)

    • 方法参数部分指定类型的参数个数是可以改变的,也就是0~n个

    • 一个方法的形参列表中最多只能声明一个可变长形参,并且需要放到参数列表的末尾

    • 示例Code

      // 自定义成员方法实现可变长参数的使用  看作一维数组使用即可  0 ~ n个
      void showArgument(int num, String... args) {
          System.out.println("num = " + num);
          for(int i = 0; i < args.length; i++) {
              System.out.println("第" + (i+1) + "个参数为:" + args[i]);
          }
      }
      
  6. 方法调用时栈内存分析

    1. 栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开始到结束会有多次方法的调用
    2. JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据
    3. 当某一个方法调用完成后,其对应的栈帧将被清除
  7. 方法传参相关

    • 调用方法时采用值传递把实参传递给形参,方法内部其实是在使用形参
    • 所谓值传递就是当参数是基本类型时,传递参数的值,比如传递i=10,真实传参时,把10赋值给了形参。当参数是对象时,传递的是对象的值,也就是把对象的地址赋值给形参
  8. 传参注意事项

    1. 基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常不会影响到实参变量的数值,因为两个变量有各自独立的内存空间
    2. 引用数据类型的变量作为方法的参数传递时,形参变量指向内容的改变会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间
    3. 当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后再改变指定的内容,则通常不会影响到实参变量指向内容的改变,因为两个变量指向不同的内存空间

二、方法和封装

1. 构造方法

  • 构造方法概述

    • 作用:使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作

    • 构造方法格式:

      class 类名 { 
          类名(形参列表) {
        构造方法体; 
        } 
      }
      

      构造方法名与类名完全相同并且没有返回值类型,连void都不许有

  • 默认构造方法

    • 当一个类中没有定义任何构造方法时,编译器会自动添加一个无参空构造构造方法,叫做默认/缺省构造方法,如:Person(){}
    • 若类中出现了构造方法,则编译器不再提供任何形式的构造方法

2. 方法重载

  • 方法重载(Overload)概述

    若方法名称相同,参数列表不同,这样的方法之间构成重载关系

  • 重载体现形式

    • 方法重载的主要形式体现在:参数的个数不同、参数的类型不同、参数的顺序不同,与返回值类型和形参变量名无关,但建议返回值类型最好相同
    • 判断方法能否构成重载的核心:调用方法时能否加以区分
  • 重载的实际意义

    方法重载的实际意义在于调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能

  • 示例Code

    /*
        编程实现方法重载主要形式的测试
     */
    public class OverloadTest {
    	
    	// 自定义成员方法
    	void show() {
    		System.out.println("show()");
    	}
    	void show(int i) { // ok  体现在方法参数的个数不同
    		System.out.println("show(int)");
    	}
    	void show(int i, double d) { // ok  体现在方法参数的个数不同
    		System.out.println("show(int, double)");
    	}
    	void show(int i, int j) { // ok  体现在方法参数的类型不同
    		System.out.println("show(int, int)");
    	}
    	void show(double d, int i) { // ok  体现在方法参数的顺序不同
    		System.out.println("show(double, int)");
    	}
    	/*
    	void show(double a, int b) { // error 与参数变量名无关
    		System.out.println("show(double, int)");
    	}
    	*/
    	/*
    	int show(double d, int i) { // error, 与返回值类型无关
    		System.out.println("show(double, int)");
    	}
    	*/
    	
    	public static void main(String[] args) {
    		
    		// 1.声明OverloadTest类型的引用指向该类型的对象
    		OverloadTest ot = new OverloadTest();
    		// 2.调用show方法
    		ot.show();
    		ot.show(66);
    		ot.show(66, 3.14);
    		ot.show(66, 118);
    		ot.show(3.14, 118);
    		//ot.show(3.14, 66);
    	}
    }
    

3. this关键字

  • this的基本概念

    • 若在构造方法中出现了this关键字,则代表当前正在构造的对象
    • 若在成员方法中出现了this关键字,则代表当前正在调用的对象
    • this关键字本质上就是当前类类型的引用变量
  • this工作原理

    在构造方法中和成员方法中访问成员变量时,编译器会加上this.的前缀,而this.相当于汉语中"我的",当不同的对象调用同一个方法时,由于调用方法的对象不同导致this关键字不同,从而this.方式访问的结果也就随之不同

  • this使用方式

    1. 当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前缀,明确要求该变量是成员变量

    2. this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以作为方法的返回值

      // 在Person类中,自定义成员方法实现Person类型对象的获取并返回的行为
      //String getName(){}
      Person getPerson() {
          // 返回当前调用对象本身  Person tp = new Person();  return tp;
          return this;
      } 
      
    3. 在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法

      //在Boy类中 自定义构造方法
      Boy() {
          // 调用本类中的有参构造方法
          //this("无名");
          System.out.println("无参构造方法!");
      }
      Boy(String name) {
          // 调用本类中的无参构造方法
          this();
          System.out.println("=========有参构造方法!");
          this.name = name;
      }
      

4. 方法递归调用

  • 递归的基本概念

    递归本质就是指在方法体的内部直接或间接调用当前方法自身的形式

  • 递归注意事项

    • 使用递归必须有递归的规律以及退出条件
    • 使用递归必须使得问题简单化而不是复杂化
    • 若递归影响到程序的执行性能,则使用递推取代之
  • 递归实例

    递归实现斐波那契数列第n项值

    int fib(int n) {
        if(n <= 1) {
            return n;
        }
        return fib(n - 1) + fib(n - 2);
    }
    

5. 封装

  • 封装概述

    对成员变量进行密封包装处理,来隐藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装

  • 封装实现流程

    • 私有化成员变量,使用private关键字修饰
    • 提供公有的get和set方法,并在方法体中进行合理值的判断
    • 在构造方法中调用set方法进行合理值的判断
  • JavaBean的概念

    • JavaBean是一种Java语言写成的可重用组件,其它Java 类可以通过反射机制发现和操作这些JavaBean 的属性

    • JavaBean本质上就是符合以下标准的Java类:

      • 类是公共的

      • 有一个无参的公共的构造器

      • 有属性,且有对应的get、set方法

  • 示例Code

    /*
        编程实现Student类的封装  封装类
     */
    public class Student {
    	
    	// 1.私有化成员变量,使用private关键字修饰
    	// private关键字修饰表示私有的含义,也就是该成员变量只能在当前类的内部使用
    	private int id;       // 用于描述学号的成员变量
    	private String name;  // 用于描述姓名的成员变量 
    	
    	// 3.在公有的构造方法中调用set方法进行合理值的判断
    	public Student() {}
    	public Student(int id, String name) {
    		//this.id = id;
    		//this.name = name;
    		setId(id);
    		setName(name);
    	}
    	
    	// 2.提供公有的get和set方法,并在方法体中进行合理值的判断
    	// 使用public关键字修饰表示公有的含义,也就是该方法可以在任意位置使用
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		if(id > 0) {
    			this.id = id;
    		} else {
    			System.out.println("学号不合理哦!!!");
    		}
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	// 自定义成员方法实现特征的打印
    	// 什么修饰符都没有叫做默认的访问权限,级别介于private和public之间
    	public void show() {
    		//System.out.println("我是" + name + ",我的学号是" + id);
    		System.out.println("我是" + getName() + ",我的学号是" + getId());
    	}
    }
    

三、static关键字和继承

1. static关键字

  • 基本概念

    • 使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关
    • static关键字修饰的成员可以使用引用.的方式访问,但推荐类名.的方式
  • 使用方式

    • 在非静态成员方法中既能访问非静态的成员又能访问静态的成员。

      (成员:成员变量 + 成员方法, 静态成员被所有对象共享)

    • 在静态成员方法中只能访问静态成员不能访问非静态成员。

      (成员:成员变量 + 成员方法, 因为此时可能还没有创建对象)

    • 在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用

      static关键字修饰。(不能滥用static关键字)

  • 构造块和静态代码块

    • 构造块:在类体中直接使用{}括起来的代码块

      每创建一个对象都会执行一次构造块构造块是先于构造方法体执行的

    • 静态代码块:使用static关键字修饰的构造块

      静态代码块随着类加载时执行一次

    • 示例Code

      /*
          编程实现构造块和静态代码块的使用
          执行顺序:1.静态代码块2.构造块3.构造方法体
       */
      public class BlockTest {
      	
      	// 当需要在执行构造方法体之前做一些准备工作时,则将准备工作的相关代码写在构造块中即可,比如:对成员变量进行的统一初始化操作,或者对final修饰的常量进行初始化
      	{
      		System.out.println("构造块!"); // (2)
      	}
      	
      	// 静态代码块会随着类的加载而准备就绪,会先于构造块执行
      	// 当需要在执行代码块之前随着类的加载做一些准备工作时,则编写代码到静态代码块中,比如:加载数据库的驱动包等
      	static {
      		System.out.println("#####################静态代码块!");   // (1)
      	}
      	
      	// 自定义构造方法
      	public BlockTest() {
      		System.out.println("====构造方法体!"); // (3)
      	}
      	
      	public static void main(String[] args) {
      		
      		BlockTest bt = new BlockTest();
      		
      		
      		BlockTest bt2 = new BlockTest();
      	}
      }
      
  • 单例设计模式

    • 概念

      在某些特殊场合中,一个类对外提供且只提供一个对象时,这样的类叫做单例类,而设计单例的流程和思想叫做单例设计模式

    • 单例设计模式实现流程

      1. 私有化构造方法,使用private关键字修饰

      2. 声明本类类型的引用指向本类类型的对象,并使用private static关键字共同修饰

      3. 提供公有的get方法负责将对象返回出去,并使用public static关键字共同修饰

    • 单例设计模式实现方式:饿汉式和懒汉式

      //饿汉式
      public class Singleton {
          private static Singleton sin = new Singleton();
      
          private Singleton() {}
      
          public static Singleton getInstance() {
              System.out.println("a new singleton object");
              return sin;
          }
      }
      
      //懒汉式
      public class Singleton2 {
          private static Singleton2 sin = null;
          
          private Singleton2() {}
          
          public static Singleton2 getInstance() {
              if(null == sin) {
                  sin = new Singleton2();
              }
              return sin;
          }
      }
      

      考虑多线程中抢占共享资源问题,开发中推荐饿汉式

2. 继承

  • 继承概述

    • 当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需要编写自己独有特征和行为的机制,叫做继承
    • 在Java语言中使用extends(扩展)关键字来表示继承关系
    • 使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件
    • 继承中父类又叫超类、基类;子类又叫派生类、孩子类
  • 继承的特点

    • 子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承只是不能直接访问
    • 无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法,来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代码super()的效果
    • Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类
  • 方法重写

    • 从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写(Override)

    • 方法重写的原则:

      • 要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许返回子类类型
      • 要求方法的访问权限不能变小,可以相同或者变大
      • 要求方法不能抛出更大的异常(异常机制)
    • 注意事项:

      父类中的静态方法在子类中的重写不是真正意义上的重写

  • 继承案例

    • 构造方法体、构造块与静态代码块在继承中的问题分析

      先执行父类的静态代码块,再执行子类的静态代码块

      执行父类的构造块,再执行父类的构造方法体

      执行子类的构造块,再执行子类的构造方法体

3. 访问控制

  • 常用的访问控制符

  • 访问控制符注意事项

    • public修饰的成员可以在任意位置使用。

    • private修饰的成员只能在本类内部使用。

    • 通常情况下,成员方法都使用public关键字修饰,成员变量都使用private关键字修饰

  • 包相关

    • 在Java语言中,用包(package)的概念来解决命名冲突的问题

    • 定义包的规范示例

      org.apache.commons.lang.StringUtil

      其中StringUtils是类名而org.apache.commons.lang是多层包名,其含义如下:

      org.apache表示公司或组织的信息(是这个公司(或组织)域名的反写);

      common 表示项目的名称信息;

      lang 表示模块的名称信息

    • 包的导入

      • 使用import关键字导入包

      • 使用import关键字导入静态成员,从Java5.0开始支持

        import static java.lang.System.out;

        out.println("xxx");

4. final关键字

  • final关键字概述

    final本意为"最终的、不可改变的",可以修饰类、成员方法以及成员变量

  • final使用方式

    • final关键字修饰类体现在该类不能被继承

      主要用于防止滥用继承,如:java.lang.String类等。

    • final关键字修饰成员方法体现在该方法不能被重写但可以被继承

      主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。

    • final关键字修饰成员变量体现在该变量必须初始化且不能改变

      主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等

  • final成员变量的初始化方式

    • 显示初始化、构造块初始化、构造方法体初始化

    • 示例Code

      public class FinalMemberTest {
      //    private final int cnt = 1; // 显式初始化
          private final int cnt;
      
          /*{
              cnt = 2;  // 构造块中进行初始化
          }*/
      
          public FinalMemberTest() {
              cnt = 3; // 构造方法体中进行初始化
          }
      
          public static void main(String[] args) {
      
              // 声明FinalMemberTest类型的引用指向该类的对象
              FinalMemberTest fmt = new FinalMemberTest();
              // 打印成员变量的数值
              System.out.println("fmt.cnt = " + fmt.cnt); // 0  1  2  3
          }
      }
      
  • 常量的概念

    在开发中很少单独使用final关键字来修饰成员变量,通常使用public static final关键字共同修饰成员变量来表达常量的含义,常量的命名规范要求是所有字母都要大写,不同的单词之间采用下划线连。

    举例:public static final double PI = 3.14;

四、多态和特殊类

1. 多态

  • 多态概述

    多态主要指同一种事物表现出来的多种形态

  • 多态的语法格式

    父类类型 引用变量名 = new 子类类型();

  • 多态的特点(使用父类类型指向子类类型对象的引用去调用方法、成员变量

    • 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法
    • 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法
    • 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)
    • 对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本
    • 对于父子类都有的成员变量,调用父类版本
  • 引用数据类型之间的转换

    • 自动类型转换:

      自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也叫做向上转型

    • 强制类型转换:

      强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也叫做向下转型或显式类型转换

    • 注意事项:

      • 引用数据类型之间的转换必须发生在父子类之间,否则编译报错

      • 若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运行阶段发生类型转换异常

      • 为了避免上述错误的发生,应该在强转之前进行判断,格式如下:

        if(引用变量 instanceof 数据类型)

        判断引用变量指向的对象是否为后面的数据类型

  • 多态的实际意义

    多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的效果

  • 多态的常见用法

    在开发中推荐使用多态的格式,此时父类类型引用直接调用的所有方法一定是父类中拥有的方法,若以后更换子类时,只需要将new关键字后面的子类类型修改而其它地方无需改变就可以立即生效,从而提高了代码的可维护性和可扩展型

    该方式的缺点就是:父类引用不能直接调用子类独有的方法,若调用则需要强制类型转换

  • 多态在方法中的使用场合

    1. 多态的使用场合一:通过参数传递形成了多态
    2. 多态的使用场合之二: 直接在方法体中使用抽象类的引用指向子类类型的对象
    3. 多态的使用场合之三:返回一个返回值类型的子类
  • 示例Code

    public class ShapeRectTest {
    
        public static void main(String[] args) {
    
            // 1.声明Shape类型的引用指向Shape类型的对象并打印特征
            Shape s1 = new Shape(1, 2);
            // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法
            // 当Rect类中重写show方法后,下面调用Shape类中的show方法
            s1.show(); // 1 2
    
            // 使用ctrl+d快捷键可以复制当前行
            System.out.println("------------------------------------");
            // 2.声明Rect类型的引用指向Rect类型的对象并打印特征
            Rect r1 = new Rect(3, 4, 5, 6);
            // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法
            // 当Rect类中重写show方法后,下面调用Rect类中的show方法
            r1.show(); // 3 4 5 6
    
            // 使用alt+shift+上下方向键  可以移动代码
            System.out.println("------------------------------------");
            // 3.声明Shape类型的引用指向Rect类型的对象并打印特征
            // 相当于从Rect类型到Shape类型的转换  也就是子类到父类的转换   小到大的转换  自动类型转换
            Shape sr = new Rect(7, 8, 9, 10);
            // 当Rect类中没有重写show方法时,下面调用Shape类中的show方法
            // 当Rect类中重写show方法后,下面的代码在编译阶段调用Shape类的方法,在运行阶段调用Rect类中的show方法
            sr.show(); // 7 8 9 10
    
            System.out.println("------------------------------------");
            // 4.测试Shape类型的引用能否直接调用父类和子类独有的方法呢???
            int ia = sr.getX();
            System.out.println("获取到的横坐标是:" + ia); // 7
            //sr.getLen();  error  Shape类中找不到getLen方法,也就是还在Shape类中查找
    
            // 调用静态方法
            sr.test(); // 提示:不建议使用引用.的方式访问
            Shape.test(); // 推荐使用类名.的方式访问
    
            System.out.println("------------------------------------");
            // 5.使用父类类型的引用调用子类独有方法的方式
            // 相当于从Shape类型到Rect类型的转换,也就是父类到子类的转换  大到小的转换   强制类型转换
            int ib = ((Rect) sr).getLen();
            System.out.println("获取到的长度是:" + ib); // 9
    
            // 希望将Shape类型转换为String类型  强制类型转换要求必须拥有父子类关系
            //String str1 = (String)sr;  Error
            // 希望将Shape类型强制转换为Circle类型,下面没有报错
            //Circle c1 = (Circle)sr; // 编译ok,但运行阶段发生  ClassCastException类型转换异常
    
            // 在强制类型转换之前应该使用instanceof进行类型的判断
            // 判断sr指向堆区内存中的对象是否为Circle类型,若是则返回true,否则返回false
            if(sr instanceof Circle) {
                System.out.println("可以放心地转换了!");
                Circle c1 = (Circle)sr;
            } else {
                System.out.println("强转有风险,操作需谨慎!");
            }
        }
    }
    

2. 抽象类

  • 抽象方法:抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体

    访问权限 abstract 返回值类型 方法名(形参列表);

  • 抽象类:抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象

  • 抽象类和抽象方法:

    • 抽象类中可以有成员变量、构造方法、成员方法
    • 抽象类中可以没有抽象方法,也可以有抽象方法
    • 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有抽象方法并且使用abstract关键字修饰的类
    • 当一个类继承抽象类后,如果不重写里面的抽象方法,就必须加上abstract也变成抽象方法
  • 抽象类的实际意义

    • 抽象类的实际意义不在于创建对象而在于被继承
    • 当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式
  • 示例Code

    public abstract class AbstractTest {
        private int cnt;
    
        public AbstractTest() {
        }
    
        public AbstractTest(int cnt) {
            setCnt(cnt);
        }
    
        public int getCnt() {
            return cnt;
        }
    
        public void setCnt(int cnt) {
            this.cnt = cnt;
        }
    
        // 自定义抽象方法
        public abstract void show();
    
        public static void main(String[] args) {
    
            // 声明该类类型的引用指向该类类型的对象
            //AbstractTest at = new AbstractTest();
            //System.out.println("at.cnt = " + at.cnt); // 0
        }
    }
    
    public class SubAbstractTest2 extends AbstractTest {
        @Override
        public void show() {
            System.out.println("使用多态方式可以提高代码的可维护性哦!");
        }
    
        public void test() {
            System.out.println("第二个子类中独有的方法!");
        }
    }
    
    public class SubAbstractTest extends AbstractTest/*, Account*/ {
        @Override
        public void show() {
            System.out.println("其实我是被迫重写的,否则我也得变成抽象的呀!");
        }
    
        public static void main(String[] args) {
    
            // 1.声明本类类型的引用指向本类类型的对象,没有多态
            SubAbstractTest sat = new SubAbstractTest();
            sat.show();
    
            System.out.println("-------------------------------");
            // 2.声明AbstractTest类型的引用指向子类的对象,形成了多态
            // 多态的使用场合之二: 直接在方法体中使用抽象类的引用指向子类类型的对象
            AbstractTest at = new SubAbstractTest2();
            // 编译阶段调用父类版本,运行阶段调用子类版本
            at.show();
            ((SubAbstractTest2) at).test();
    
            System.out.println("-------------------------------");
            SubAbstractTest2 sat2 = new SubAbstractTest2();
            sat2.test();
        }
    }
    

3. 接口

  • 接口概述

    • 接口就是一种比抽象类还抽象的类,体现在所有方法都为抽象方法
    • 定义类的关键字是class,而定义接口的关键字是interface
  • 类和接口之间的关系

  • 抽象类和接口的主要区别

    • 定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
    • 继承抽象类的关键字是extends,而实现接口的关键字是implements。
    • 继承抽象类支持单继承,而实现接口支持多实现。
    • 抽象类中可以有构造方法,而接口中不可以有构造方法。
    • 抽象类中可以有成员变量,而接口中只可以有常量
    • 抽象类中可以有成员方法,而接口中只可以有抽象方法。
    • 抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写(Java8以前的版本)。
    • 从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非抽象方法需要使用default关键字修饰
    • 从Java9开始增加新特性,接口中允许出现私有方法
    • 抽象类中抽象方法不可以省略public abstract, 接口中的抽象方法可以省略public abstract
  • 回调模式

    • 回调模式概述

      回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的)

    • 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:

      1. 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递

      2. 使用上述匿名内部类的语法格式得到接口/类类型的引用即可

  • 示例Code

    接口中可以定义常量,但不是成员变量;接口中抽象方法简化写法

    public interface InterfaceTest {
        /*public static final */int CNT = 1;  // 里面只能有常量
        //private void show(){}  // 从Java9开始允许接口中出现私有方法
        /*public abstract */void show();         // 里面只能有抽象方法(新特性除外),注释中的关键字可以省略,但建议写上
    }
    

    父子接口继承案例

    public interface Runner {
        // 自定义抽象方法描述奔跑的行为
        public abstract void run();
    }
    
    // 接口只能继承接口,不能继承类
    public interface Hunter extends Runner {
        // 自定义成员方法描述捕猎的行为
        public abstract void hunt();
    
        // 将两个默认方法中重复的代码可以提取出来打包成一个方法在下面的两个方法中分别调用即可
        private void show() {
            System.out.println("在以后的开发中尽量减少重复的代码,也就是减少代码的冗余!");
        }
        // 增加一个抽象方法
        //public abstract void show1();
        // 增加非抽象方法
        public default void show1() {
            show();
            //System.out.println("在以后的开发中尽量减少重复的代码,也就是减少代码的冗余!");
            System.out.println("show1方法中:这里仅仅是接口中的默认功能,实现类可以自由选择是否重写!");
        }
    
        // 增加非抽象方法
        public default void show2() {
            show();
            //System.out.println("在以后的开发中尽量减少重复的代码,也就是减少代码的冗余!");
            System.out.println("show2方法中:这里仅仅是接口中的默认功能,实现类可以自由选择是否重写!");
        }
    
        // 增加静态方法 隶属于类层级,也就是接口层级
        public static void test() {
            System.out.println("这里是静态方法,可以直接通过接口名.的方式调用,省略对象的创建");
        }
    }
    

    父子接口继承案例中的实现类

    public class Man implements Hunter {
        @Override
        public void hunt() {
            System.out.println("正在追赶一直小白兔...");
        }
    
        @Override
        public void run() {
            System.out.println("正在被一直大熊追赶,玩命奔跑中...");
        }
    
        @Override
        public void show1() {
            System.out.println("为了给你几分薄面,我决定重写一下!");
        }
    
        public static void main(String[] args) {
    
            // 1.声明接口类型的引用指向实现类的对象,形成了多态
            Runner runner = new Man();
            runner.run();
    
            Hunter hunter = new Man();
            hunter.hunt();
    
            System.out.println("-----------------------------------------");
            // 2.可以使用接口名称.的方式调用接口中的静态方法
            Hunter.test();
        }
    }
    

五、特殊类

1. 内部类

  • 内部类概述

    • 当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)
    • 类中的内容:成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类
  • 内部类实际作用

    当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法

  • 内部类的分类

    • 普通内部类 - 直接将一个类的定义放在另外一个类的类体中

    • 静态内部类 - 使用static关键字修饰的内部类,隶属于类层级

    • 局部内部类 - 直接将一个类的定义放在方法体的内部时

    • 匿名内部类 - 就是指没有名字的内部类

  • 普通内部类

    • 语法格式

      访问修饰符 class 外部类的类名 {
      	访问修饰符 class 内部类的类名 { 
              内部类的类体;
          } 
      }
      
    • 内部类的使用方式

      • 普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等

      • 普通内部类和普通类一样可以使用final或者abstract关键字修饰

      • 普通内部类还可以使用privateprotected关键字进行修饰

      • 普通内部类需要使用外部类对象来创建对象

      • 如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字

    • 示例Code

      /**
       * 编程实现普通内部类的定义和使用       -  文档注释
       */
      public class NormalOuter {
          private int cnt = 1;
      
          // 定义普通内部类,隶属于外部类的成员,并且是对象层级
          /*private*/public /*final*/ class NormalInner {
              private int ia = 2;
              private int cnt = 3;
              public NormalInner() {
                  System.out.println("普通内部类的构造方法体执行到了!");
              }
      
              public void show() {
                  System.out.println("外部类中变量cnt的数值为:" + cnt); // 1
                  System.out.println("ia = " + ia); // 2
              }
      
              public void show2(int cnt) {
                  System.out.println("形参变量cnt = " + cnt);  // 局部优先原则  4
                  System.out.println("内部类中cnt = " + this.cnt); // 3
                  System.out.println("外部类中cnt = " + NormalOuter.this.cnt); // 1
              }
          }
      }
      
      public class NormalOuterTest {
      
          public static void main(String[] args) {
      
              // 1.声明NormalOuter类型的引用指向该类型的对象
              NormalOuter no = new NormalOuter();
              // 2.声明NormalOuter类中内部类的引用指向内部类的对象
              NormalOuter.NormalInner ni = no.new NormalInner();
              // 调用内部类中的show方法
              ni.show();
      
              System.out.println("---------------------------------------------");
              ni.show2(4);
          }
      }
      
  • 静态内部类

    • 语法格式

      访问修饰符 class 外部类的类名 {
      	访问修饰符 static class 内部类的类名 { 
              内部类的类体;
      	} 
      }
      
    • 静态内部类的使用方式

      • 静态内部类不能直接访问外部类的非静态成员

      • 静态内部类可以直接创建对象

      • 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问

    • 示例Code

      /**
       * 实现静态内部类的定义和使用
       */
      public class StaticOuter {
          private int cnt = 1;        // 隶属于对象层级
          private static int snt = 2; // 隶属于类层级
      
          public /*static*/ void show() {
              System.out.println("外部类的show方法就是这里!");
          }
      
          /**
           * 定义静态内部类   有static关键字修饰隶属于类层级
           */
          public static class StaticInner {
              private int ia = 3;
              private static int snt = 4;
      
              public StaticInner() {
                  System.out.println("静态内部类的构造方法哦!");
              }
      
              public void show() {
                  System.out.println("ia = " + ia); // 3
                  System.out.println("外部类中的snt = " + snt); // 2
                  //System.out.println("外部类的cnt = " + cnt); // Error:静态上下文中不能访问非静态的成员,因此此时可能还没有创建对象
              }
      
              public void show2(int snt) {  // 就近原则
                  System.out.println("snt = " + snt); // 5
                  System.out.println("内部类中的成员snt = " + StaticInner.snt); // 4
                  System.out.println("外部类中的成员snt = " + StaticOuter.snt); // 2
                  //StaticOuter.show();
                  new StaticOuter().show();
              }
          }
      }
      
      public class StaticOuterTest {
      
          public static void main(String[] args) {
      
              // 1.声明StaticInner类型的引用指向该类型的对象
              StaticOuter.StaticInner si = new StaticOuter.StaticInner();
              // 2.调用show方法进行测试
              si.show();
      
              System.out.println("---------------------------------------------");
              si.show2(5);
          }
      }
      
  • 局部内部类

    • 语法格式

      访问修饰符 class 外部类的类名 {
      	访问修饰符 返回值类型 成员方法名(形参列表) {
      		class 内部类的类名 { 
                  内部类的类体; 
              }
      	} 
      }
      
    • 局部内部类的使用方式

      • 局部内部类只能在该方法的内部可以使用

      • 局部内部类可以在方法体内部直接创建对象

      • 局部内部类不能使用访问控制符和static关键字修饰符

      • 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致

    • 示例Code

      /**
       * 编程实现局部内部类的定义和使用
       */
      public class AreaOuter {
          private int cnt = 1;
      
          public void show() {
      
              // 定义一个局部变量进行测试,从Java8开始默认理解为final关键字修饰的变量
              // 虽然可以省略final关键字,但建议还是加上
              final int ic = 4;
      
              // 定义局部内部类,只在当前方法体的内部好使    拷贝一份
              class AreaInner {
                  private int ia = 2;
      
                  public AreaInner() {
                      System.out.println("局部内部类的构造方法!");
                  }
      
                  public void test() {
                      int ib = 3;
                      System.out.println("ia = " + ia); // 2
                      System.out.println("cnt = " + cnt); // 1
                      //ic = 5;  Error
                      System.out.println("ic = " + ic); // 4
                  }
              }
      
              // 声明局部内部类的引用指向局部内部类的对象
              AreaInner ai = new AreaInner();
              ai.test();
          }
      
      }
      
      public class AreaOuterTest {
      
          public static void main(String[] args) {
      
              // 1.声明外部类类型的引用指向外部类的对象
              AreaOuter ao = new AreaOuter();
              // 2.通过show方法的调用实现局部内容类的定义和使用
              ao.show();
          }
      }
      
  • 匿名内部类

    • 语法格式

      接口/父类类型 引用变量名 = new 接口/父类类型() { 
          方法的重写 
      };
      
    • 示例Code

      public interface AnonymousInterface {
          // 自定义抽象方法
          public abstract void show();
      }
      
      public class AnonymousInterfaceImpl implements AnonymousInterface {
          @Override
          public void show() {
              System.out.println("这里是接口的实现类!");
          }
      }
      
      public class AnonymousInterfaceTest {
      
          // 假设已有下面的方法,请问如何调用下面的方法?
          // AnonymousInterface ai = new AnonymousInterfaceImpl();
          // 接口类型的引用指向实现类型的对象,形成了多态
          public static void test(AnonymousInterface ai) {
              // 编译阶段调用父类版本,运行调用实现类重写的版本
              ai.show();
          }
      
          public static void main(String[] args) {
      
              //AnonymousInterfaceTest.test(new AnonymousInterface()); // Error:接口不能实例化
              AnonymousInterfaceTest.test(new AnonymousInterfaceImpl());
      
              System.out.println("---------------------------------------------------------------");
              // 使用匿名内部类的语法格式来得到接口类型的引用,格式为:接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
              AnonymousInterface ait = new AnonymousInterface() {
                  @Override
                  public void show() {
                      System.out.println("匿名内部类就是这么玩的,虽然你很抽象!");
                  }
              };
      
              // 从Java8开始提出新特性lambda表达式可以简化上述代码,格式为:(参数列表) -> {方法体}
              AnonymousInterface ait2 = () -> System.out.println("lamda表达式原来是如此简单!");
              AnonymousInterfaceTest.test(ait2);
          }
      }
      

2. 枚举

  • 枚举概述

    • 一年中的所有季节:春季、夏季、秋季、冬季。

      所有的性别:男、女。

      键盘上的所有方向按键:向上、向下、向左、向右

      在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型

    • 使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型

    • 枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用枚举类型.的方式调用

    • 枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默认也是私有的

  • Enum类

    • 所有的枚举类都继承自java.lang.Enum

    • Enum类中方法

  • 枚举类实现接口的方式

    枚举类实现接口后需要重写抽象方法,而重写方法的方式有两种:单独重写一个,或者每个常量定义中的对象都重写

3. 注解

  • 注解的概述

    • 注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型

    • 注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理

  • 注解的语法格式

    访问修饰符 @interface 注解名称 {
    	注解成员; 
    }
    

    自定义注解自动继承java.lang.annotation.Annotation接口

    通过@注解名称的方式可以修饰包、类、 成员方法、成员变量、构造方法、参数、局部变量的声明等

  • 注解的使用方式

    • 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型

    • 如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型

    • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可

      例如:@SuppressWarnings("all"),这个注解中只有一个属性String[] value()

    • 数组赋值时,值使用{}包裹,如果数组中只有一个值,则{}省略

  • 元注解概述

    • 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面

    • 元注解主要有 @Retention、@Documented、@Target、@Inherited、 @Repeatable

    • 元注解@Retention应用到一个注解上用于说明该注解的的生命周期,取值如下:

      • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视

      • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中,默认方式

      • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们

    • 元注解@Documented

      • 使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容。

      • @Documented用于指定被该注解将被javadoc工具提取成文档。

      • 定义为@Documented的注解必须设置Retention值为RUNTIME

    • 元注解@Target用于指定被修饰的注解能用于哪些元素的修饰

    • 元注解@Inherited

      @Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解

    • 元注解@Repeatable

      • @Repeatable表示自然可重复的含义,从Java8开始增加的新特性。

      • 从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:

        1. 其中ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型
        2. 其中ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中
    • 示例Code

      import java.lang.annotation.*;
      
      //@Retention(RetentionPolicy.SOURCE)     // 表示下面的注解在源代码中有效
      //@Retention(RetentionPolicy.CLASS)      // 表示下面的注解在字节码文件中有效,默认方式
      @Retention(RetentionPolicy.RUNTIME)      // 表示下面的注解在运行时有效
      @Documented                              // 表示下面的注解信息可以被javadoc工具提取到API文档中,很少使用
      // 表示下面的注解可以用于类型、构造方法、成员变量、成员方法、参数 的修饰
      //{}表示一维数组静态初始化的省略格式
      @Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
      @Inherited  // 表示下面的注解所修饰的类中的注解使用可以被子类继承
      // 若一个注解中没有任何的成员,则这样的注解叫做标记注解/标识注解
      public @interface MyAnnotation {
          //public Direction value(); // 声明一个String类型的成员变量,名字为value   类型有要求
          
          //default表示可以设置一个该成员变量的初始值,在使用时可以不用再赋值
          public String value() default "123"; // 声明一个String类型的成员变量,名字为value
          public String value2();
      }
      
      // 表示将标签MyAnnotation贴在Person类的代码中,使用注解时采用 成员参数名 = 成员参数值, ...
      //@MyAnnotation(value = "hello", value2 = "world")
      @MyAnnotation(value2 = "world")
      public class Person {
          /**
           * name是用于描述姓名的成员变量
           */
          @MyAnnotation(value2 = "1")
          private String name;
          /**
           * age是用于描述年龄的成员变量
           */
          private int age;
      
          /**
           * 编程实现无参构造方法
           */
          @MyAnnotation(value2 = "2")
          public Person() {
          }
      
          /**
           * 编程实现有参构造方法
           * @param name
           * @param age
           */
          public Person(@MyAnnotation(value2 = "4") String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          /**
           * 自定义成员方法实现特征的获取和修改
           * @return
           */
          @MyAnnotation(value2 = "3")
          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;
          }
      }
      
  • 预制注解

    • 预制注解就是Java语言自身提供的注解

    • 常见的预制注解


posted @ 2022-02-14 11:03  Ramentherapy  阅读(133)  评论(0)    收藏  举报