面向对象编程
类和对象
概念
面型对象编程的概念
- 万物皆对象
- 面向对象指以属性和行为的观点去分析现实生活中的事物
- 面向对象编程指先以面向对象的思想进行分析,然后使用面向对象的编程语言进行表达的过程
- 面向对象编程是软件产业化发展的需求
- 理解面向对象的思想精髓(封装、继承、多态)
类和对象概念
对象
- 对象是现实生活中客观存在的实体,在Java语言中对象体现为内存空间中的一块存储区域
类
- 类简单来说就是“分类”,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性的成员变量以及描述行为的成员方法
- 类是用于构建对象的模板,对象的数据结构由定义它的类来决定
类与对象的定义和使用
类和对象以及引用的定义
类的定义
-
class 类名 { 类体; }
-
注意命名规范:通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写
成员变量的定义
-
class 类名 { 数据类型 成员变量名 = 初始值; }
-
注意命名规范:当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首字母大写
对象的创建
- new 类名();
- 注意
- 当一个定义完毕后,可以使用new关键字来创建该类的对象,这个过程叫做类的实例化
- 创建对象的本质就是在内存空间的堆区申请一块存储区域,用于存放该对象独有特征信息
引用的定义
-
基本概念
- 使用引用数据类型定义的变量叫做引用型变量,简称为“引用”
- 引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问(实际值存在堆区中)
-
语法格式
类名 引用变量名;
引用变量名.成员变量名;
//举例 Person p = new Person(); p.name = "张飞"; System.out.println(p.name);
创建引用变量(对象)时,在堆区中存储引用变量(对象)的具体数据,在栈区中存储堆区对应的内存地址
引用变量的注意事项
- 引用类型变量用于存放对象的地址,可以给引用类型赋值为null,表示不指向任何对象
- 当某个引用类型变量为null时无法对对象实施访问(因为它没有指向任何对象)。此时,若通过引用访问成员变量或调用方法,会产生NullPointerException异常
类与对象代码执行流程和内存分析
注:代码写完之后放在硬盘中,编译运行后会将源码放在内存中(即方法区)。引用类型变量声明之后在栈区中声明一个空间,之后在堆区中声明一片对应空间用于存放引用类型变量的具体数据(成员变量值等),将堆区的这片区域地址放在栈区中进行索引
类的成员方法
成员方法的定义
class 类名 {
返回值类型 成员方法名(形参列表) {
成员方法体;
}
}
-
当成员方法名由多个单词组成时,要求从第二个单词起每个单词的首字母大写
-
返回值
- 返回值主要指从方法体内返回到方法体外的数据内容返回值类型主要指返回值的数据类型,可以是基本数据类型,也可以是引用数据类型
- 使用return关键字返回具体的数据内容并结束当前方法。当方法不需要返回任何数据内容时,将返回数据类型声明为void即可
-
形参列表
-
形式参数主要用于将方法体外的数据内容带入到方法体内部
-
形式参数列表主要指多个形式参数组成的列表,语法格式如下:
数据类型 形参变量名1,数据类型 形参变量名2,…
-
当该方法不需要带入任何数据内容时,则形参列表位置啥也不写即可
-
-
方法体:用于编写描述该方法功能的语句块
-
成员方法可以实现代码的重用,简化代码
成员方法的调用
- 引用变量名.成员方法名(实参列表);
- 实际参数列表主要用于对形式参数列表进行初始化操作,因此参数的个数、类型以及顺序都要完全一致
- 实际参数可以传递直接量、变量、表达式、方法的调用
- 调用方法的本质就是根据方法名跳转过去执行方法体后再跳转回当前位置
可变长参数
- 返回值类型 方法名(参数的类型… 参数名)
- 方法参数部分指定类型的参数个数是可以改变的,也就是0~n个
- 一个方法的形参列表中最多只能声明一个可变长形参,并且需要放到参数列表的末尾(注意是可变长参数只能有一个并且要放到末尾,正常形参还是可以有多个的)
- 可变长参数看作一维数组使用即可:循环遍历多个参数使用
方法的传参过程
-
方法传参的过程图
-
参数传递的注意事项
-
基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常不会影响到实参变量的数值,因为两个变量有各自独立的内存空间–>只传递值,不传递地址;
-
引用数据类型的变量作为方法的参数传递时,形参变量指向内容的改变会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间(因为指向的是地址)
-
当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后再改变指定的内容(比如在方法中创建一个新的引用空间赋值给这个形参),则通常不会影响到实参变量指向内容的改变,因为两个变量指向不同的内存空间
void show(int[] arr1) { arr1 = new int[10]; //传入实参arr2之后,arr2指向最开始的堆区,arr1已经指向一个新的堆区 arr1[0] = 200; }
-
内存结构之栈区
- 栈用于存放程序运行过程中所有的局部变量。一个运行的Java程序从开始到结束会有多次方法的调用
- JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据
- 当某一个方法调用完成后,其对应的栈帧将被清除
总结
- 面向对象编程的概念(理解)
- 对象、面向对象、面向对象编程等
- 类和对象以及引用(重中之重)
- 类和对象、类的定义、成员变量的定义、对象的创建、引用的定义等
- 成员方法(重中之重)
- 语法格式、详解、调用格式、传参的过程等
方法和封装
构造方法
构造方法的基本概念
-
class 类名 { 类名(形参列表) { 构造方法体; } }
-
构造方法名与类名完全相同并且没有返回值类型,连void都不许有
默认构造方法
- 当一个类中没有定义任何构造方法时,编译器会自动添加一个无参空构造方法,叫做默认/缺省构造方法,如:Person(){}
- 若类中出现了构造方法,则编译器不再提供任何形式的构造方法
- 总结:类中没有提供构造方法时,则创建对象时调用默认构造方法;若类中提供构造方法后,则创建对象时调用类中提供的对应构造方法
构造方法的作用
- 使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作
方法的重载
方法重载的概念
- 若方法名称相同,参数列表不同,这样的方法之间构成重载关系(Overload)
重载的体现形式
- 方法重载的主要形式体现在:参数的个数不同、参数的类型不同、参数的顺序不同,与返回值类型和形参变量名无关,但建议返回值类型最好相同
- 判断方法能否构成重载的核心:调用方法时能否加以区分
方法重载的实际意义
- 方法重载的实际意义在于调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能(构造方法必须重载)
- 如:java.io.PrintStream类中的println()方法
this关键字
this的基本概念
- 若在构造方法中出现了this关键字,则代表当前正在构造的对象
- 若在成员方法中出现了this关键字,则代表当前正在调用的对象(即:谁调用成员方法,代表的就是谁)
this关键字的工作原理
-
在构造方法或者成员方法中访问成员变量时,编译器会加上this.的前缀,而this.相当于汉语中的“我的”,当不同的对象调用同一个方法时,由于调用方法的对象不同导致this关键字不同,从而this.方式访问的结果也就随之不同
void show() { //show()是一个成员方法 System.out.println("我叫" + name + ",今年:" + age + "岁"); /*虽然name和age在成员方 法中能直接访问,但是其实这是因为隐藏了this.,实际应该是this.name和this.age,因此不需要对象名也 能直接访问成员变量name和age*/ }
this关键字的使用方式
-
当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前缀,明确要求该变量是成员变量(重中之重)
Person(String name, int age) { //形参name和age与成员变量同名 this.name = name; //使用this区分何者是成员变量 this.age = age; }
-
this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以作为方法的返回值(重点)
Person getPerson() { //getPerson()是成员方法 return this; //返回的就是当前对象 }
-
在构造方法的第一行可以使用this()的方式来调用本类中的其他构造方法(用的比较少,了解)
Person() { this("无名"); //将会先调用本类的有参构造方法 System.out.println("无参构造方法"); } Person(String name) { this.name = name; System.out.println("有参构造方法"); }
递归
递归的基本概念
- 递归的本质是指在方法体的内部直接或间接调用当前方法自身的形式
递归的使用
class Recursion {
int factorial(int n) { //递归实现计算n的阶乘
if(n == 1) return 1;
return n * factorial(n - 1);
}
int fibonacci(int n) { //递归实现计算斐波那契数列第n个位置的值
if(n == 1 || n == 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int fib(int n) { //递推实现计算斐波那契数列第n个位置上的值
int ia = 1;
int ib = 1;
int ic;
for(int i = 3; i <= n; i++) {
ic = ia + ib;
ia = ib;
ib = ic;
}
return ib;
}
public static void main(String[] args) {
Recursion re = new Recursion();
int fac = re.factorial(5);
int fi = re.fibonacci(5);
}
}
递归方式的原理分析
- 一层层进入到终点为止,再一层层退出—>套娃
使用递归注意事项
- 使用递归必须有递归的规律以及退出条件
- 使用递归必须使得问题简单化而不是复杂化
- 若递归影响到程序的执行性能,则使用递推(正常顺序)取代之
代码的拆分实现
- 实际开发中将main()函数放在一个专门的测试类中:一般这个测试类的名字以Test结尾,如XxxTest.java
- 其他的类称之为功能类/封装类
- 编译运行main()函数所在类即可
封装
封装的概念
- 通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无论是编译阶段还是运行阶段都不会报错或者给出提示,此时与现实生活不符
- 为了避免上述错误的发生,就需要对成员变量进行密封包装处理,来隐藏成员变量的细节以及保证成员变量数值的合理性(即:不能随心就可以访问到),该机制就称之为封装
封装的实现
- step1、私有化成员变量,使用private关键字修饰
- public:表示公有,可以在任意位置使用该方法
- 什么修饰符都没有:默认的访问权限,级别介于private和public之间
- step2、提供公有的get()和set()方法,并在方法体中进行合理值的判断
- step3、可以在公有的构造方法中调用set()方法进行合理值的判断
JavaBean的概念(了解)
- JavaBean是一种Java语言写成的可重用组件,其他Java类可以通过反射机制发现和操作这些JavaBean的属性
- JavaBean本质上就是符合以下标准的Java类(封装类):
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
案例:编程实现数个学生的信息录入和打印
-
功能类/封装类:Student类
class Student { private String name; private int id; public Student() { //无参的构造方法 } public Student(String name, int id) { //使用set()方法配置成员变量值可以进行成员变量的合理性判断 setId(id); setName(name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return this.id; } public void setId(int id) { if(id > 0) { this.id = id; } else { System.out.println("输入的id有误,请重新输入"); } } //输出方法 public void show() { System.out.println("学生姓名:" + name + ",id是:" + id); } }
-
测试类:放main()方法的StudentTest类
import java.util.Scanner; public class StudentTest { public static void main(String[] args) { //1、提示用户输入学生的人数并用变量记录 System.out.println("请输入学生的人数:"); Scanner sc = new Scanner(System.in); int num = sc.nextInt(); //2、根据学生人数准备一组对应的一维数组并录入 Student[] stus = new Student[num]; //注意:每个一维数组元素也是一个Student类对象,因此要每次都要记得new开辟内存空间存储 for(int i = 0; i < num; i++) { System.out.println("请输入第" + (i + 1) + "个学生的信息:(学号 id)"); stus[i] = new Student(sc.next(), sc.nextInt()); } System.out.println("-----------输出学生信息-------------------"); for(int i = 0; i < num; i++) { stus[i].show(); } } }
-
Student数组的原理分析结构图
总结
- 构造方法(重中之重)
- 语法格式、默认构造方法、实现成员变量的初始化
- 方法重载(重点)
- 概念、体现形式、实际意义
- this关键字(原理)
- 概念、原理、使用方式
- 递归(难点)
- 概念、使用原则
- 封装(重中之重)
- 概念、实现流程
static关键字和继承
static关键字
static关键字的基本概念
- 使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级升级为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关。
- 使用:static关键字修饰的成员可以使用 引用. 的方法访问,但推荐 类名. 的方式
static关键字(country)的内存结构图
static关键字的使用方式
-
在非静态成员方法中既能访问非静态的成员又能访问静态的成员
(成员:成员变量 + 成员方法,因为静态成员被所有对象共享)
-
在静态成员方法中只能访问静态成员不能访问非静态成员(静态成员方法中没有this.关键字,因为是通过 类名. 进行访问的)
(成员:成员变量 + 成员方法,因为此时可能还没有创建对象)
public static void setCountry(String country) { People.country = country; //country是静态成员变量,注意是通过 类名. 进行访问的 }
-
在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用static关键字修饰(不能滥用static关键字)
构造块和静态代码块
构造块
- 什么是构造块:在类体中直接使用{}括起来的代码块
- 每创建一个对象都会执行一次构造块
- 构造块的作用:当需要在执行构造方法体之前做一些准备工作时,则将准备工作的相关代码写在构造块中即可,比如:对成员变量进行的统一初始化操作
静态代码块
- 静态代码块:使用static关键字修饰的构造块
- 静态代码块随着类加载时执行一次
- 静态代码块作用:当需要在执行代码块之前随着类的加载做一些准备工作时,则编写代码到静态代码块中,比如:加载数据库的驱动包等
执行次序
先执行静态代码块(静态代码块只会执行一次);之后执行构造块(每调用一次构造方法,即创建一个对象就会执行构造块);然后再执行构造方法体
又见构造块和静态代码块(笔试考点)
父类和子类中的构造块和静态代码块执行次序
- 先执行父类的静态代码块,再执行子类的静态代码块(因为静态代码块一定先加载调用)
- 执行父类的构造块,执行父类的构造方法体(因为构造块在调用构造方法时调用(之前))
- 执行子类的构造块,执行子类的构造方法体
单例设计模式
案例
- 编程实现Singleton类的封装
- 编程实现SingletonTest类对Singleton类进行测试,要求main方法中能得到且只能得到该类的一个对象
Singleton类
public class Singleton {
//2、声明本类类型的引用指向本类类型的对象,使用private static关键字共同修饰
private static Singleton sin = new Singleton(); //饿汉式
//private static Singleton sin = null; //懒汉式
//1、私有化构造方法,使用private关键字修饰-->这样讲无法再随意(使用构造函数)创建对象
private Singleton() {}
//3、提供公有的get方法将对象返回出去-->使用static可以用类名直接访问,实现只创建一个对象
public static Singleton getInstance() { //只获取一次Singleton对象
return sin;
/*if(sin == null) { //懒汉式
sin = new Singleton();
}
return sin;*/
}
}
SingletonTest类
public class SingletonTest {
public static void main(String[] args) {
/*
Singleton s1 = new Singleton();
Singleton s2 = new Singleton();
System.out.println(s1 == s2); //输出false,因为两个都是新创建的对象,在堆区中的地址不同,引用自然不等
*/
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); //输出true,只创建一个对象
}
}
案例的内存结构图
单例设计模式的概念
- 在某些特殊场合中,一个类对外提供且只提供一个对象时,这样的类叫做单例类,而设计单例的流程和思想叫做单例设计模式
单例设计模式的实现流程
- 私有化构造方法,使用private关键字修饰
- 声明本类类型的引用指向本类类型的对象,并使用private static关键字共同修饰
- 提供公有的get()方法负责将对象返回出去,并使用public static关键字共同修饰
单例设计模式的实现方式
- 单例设计模式的实现方式有两种:饿汉式 和 懒汉式,在以后的开发中推荐使用饿汉式
- 饿汉式:声明本类类型的引用时就创建对象,让引用指向对象
- 懒汉式:声明本类类的的引用时先让它指向null,在之后的get()方法中判断引用为null时才创建对象让引用指向该对象
继承
继承的概念
- 当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,让多个类吸收公共类中已有的特征和行为,而在多个类型只需要编写自己独有特征和行为的机制,叫做继承
继承的使用
-
在Java语言中使用extends关键字来表示继承关系
-
如:
public class Worker extends Person{} //表示Worker类继承自Person类
其中Person类叫做超类、父类、基类
其中Worker类叫做派生类、子类、孩子类
-
继承的意义
- 使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件
继承的特点
- 子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承,只是不能直接访问(使用公有的get()方法获得)
- 无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法(相当于super()),来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代码super()的效果
- super的效果同this关键字差不多,表示父类对象–>super()表示父类对象,super. 调用调用父类的成员
- super()和this()都必须放第一行,因此两个不能共存
- 使用继承必须满足逻辑关系:子类 is a 父类,也就是不能滥用继承
- Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类,也可以有祖先和后代的关系(称为间接子类)
方法重写
方法重写的概念
- 从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写(Override)
- 方法重写(Override)和方法重载(Overload)区别
- 方法重写是建立在继承上的,重新写从父类中继承下来的方法
- 方法重载是写一个方法名相同,形参不同的新方法
方法重写的使用
- 直接写上和父类一样的方法,在方法体中实现自己希望实现的效果
- 若想直接使用父类的方法体中内容,在重写的方法中写上super.show();
- 在重写的方法体上面写上注解@Override:用于说明下面的方法是对父类方法的重写,若没有构成重写则编译报错
- 当方法重写后,子类对象调用方法,调用的是重写后的方法
- 方法重写只在子类需要往该方法新功能时重写,否则使用继承自父类的方法即可
方法重写的原则
- 要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许返回子类类型
- 要求方法的访问权限不能变小,可以相同或者变大
- 要求方法不能抛出更大的异常(异常机制)
权限修饰符和包
包
package语句的由来
- 定义类时需要指定类的名称,但如果仅仅将类名作为类的唯一标识,则不可避免地出现类名冲突地问题。这会给组件复用以及团队间的合作造成很大的麻烦!
- 在Java语言中,用包(package)的概念来解决命名冲突的问题
包的定义
-
在定义一个类时,除了定义类的名称一般还要指定一个包名,格式如下:
package 包名; //创建单层包
package 包名1.包名2.包名3…包名n; //创建多层包
-
为了实现项目管理、解决命名冲突以及权限控制的效果
定义包的规范
- 如果各个公司或开发组织的程序员都随心所欲地命名包名的话,仍然不能从根本上解决命名冲突的问题。因此,在指定包名的时候应该按照一定的规范
- 如:org.apache.commons.lang.StringUtil
- org.apache表示公司或者组织的信息(是这个公司(或组织)域名的反写)
- commons表示项目的名称信息
- lang表示模块的名称信息
- StringUtil是类名
包的导入
-
使用import关键字导入包
-
使用import关键字导入静态成员,从Java5.0开始支持
import java.lang.System.out; //导入java目录中lang目录下System类中的静态成员out
导入后输出语句可以写成:
out.println("输出语句");
笔试可能考到,开发中基本不这么使用
权限修饰符
注意事项
- public修饰的成员可以在任意位置使用
- private修饰的成员只能在本类内部使用
- 通常情况下,成员方法都使用public关键字修饰,成员变量都使用private关键字修饰
final关键字
基本概念
- final本意为”最终的、不可改变的“,可以修饰类、成员方法以及成员变量
使用方式
-
final关键字修饰类体现在该类不能被继承
- 如:public final class String {…}
- 主要用于防止滥用继承,如:java.lang.String类等
-
final关键字修饰成员方法体现在该方法不能被重写但可以被继承(也可以被重载)
- 主要用于防止不经意间造成重写,如:java.text.Dateformat类中的format方法等
-
final关键字修饰成员变量体现在该成员变量必须被初始化且不能被改变
-
如:
private final int cnt = 1; //方式一:显式初始化 private final int cmt; { cmt = 2; //方式二:构造块中进行初始化(因为构造块是在创建对象时就会被调用) } public FinalMemberTest() { cat = 3; //方式三:构造方法体中进行初始化 }
-
主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等
-
常量
- 在以后的开发中很少单独使用final关键字来修饰成员变量,通常使用public static final关键字共同修饰成员变量来表达常量的含义(”变为常量“),常量的命名规范要求所有字母都要大写,不同的单词之间采用下划线连接
- 如:public static final double PI = 3.14;
总结
-
static关键字
概念、使用方式、构造块和静态代码块、单例设计模式–>饿汉式和懒汉式(推荐饿汉式)
-
继承
概念、特点、方法的重写、重写的原则、IDEA的使用等
-
访问控制权限
public、private、package、导入等
-
final关键字
概念、修饰类、成员方法、成员变量、常量等
多态和特殊类
多态
多态的概念
- 多态主要指同一种事物表现出来的多种形态
- 饮料:可乐、雪碧、红牛、脉动…
- 宠物:猫、狗、鸟、小强、鱼…
- 人:学生、教师、工人、保安…
- 图形:矩形、圆形、梯形、三角形…
多态的语法格式
- 父类类型 引用变量名 = new 子类类型(); //父类引用指向子类对象
- 如:
Shape sr = new Rect(); sr.show();
多态的特点
- 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法
- 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法
- 对于父子类都有的非静态方法来说(当多态遇上方法重写),编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)
- 对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本(静态方法是没有方法重写这一回事的,因此若在子类上写上方法注解@Override将会报错。静态方法推荐使用的是用 类名. 直接调用,因此静态方法是属于类层级的,与所创建的对象无关,故而都是调用父类版本)
引用数据类型之间的转换
转换方式
- 引用数据类型之间的转换方式有两种:自动类型转换 和 强制类型转换
- 自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也叫做向上转型
- 强制类型转换主要指大类型向小类型转换,也就是父类转为子类,也叫做向下转型或显式类型转换
- 语法:
((子类类型) 父类引用).成员
- 语法:
注意事项
-
引用数据类型之间的转换必须发生在父子类之间,否则会编译报错
-
若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运行阶段发生类型转换异常
Shapr sr = new Rect(); //多态 Circle cr = (Circle) sr; //sr虽然是父类Shape的引用,但是本质是Rect类型的对象(编译阶段调父 //类,运行阶段调子类),因此编译阶段ok(出现黄色警告),但运行阶段发生ClassCastException类型转换异常
-
为了避免上述错误的发生,应该在强转之前进行判断(使用instanceof关键字),格式如下:
if (引用变量 instanceof 数据类型)
判断引用变量指向的对象是否为后面的数据类型
Shape sr = new Rect(); if (sr instanceof Circle) { //判断sr引用是否是指向Circle对象 System.out.println("可以放心地转换了!"); Circle cr = (Circle) sr; } else { System.out.println("强转有风险,操作需谨慎!"); }
多态的实际意义
-
多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的效果(函数形参是父类,实参可以用不同子类传入)
public class ShaprTest { public static void draw(Shape s) { //形参是父类 s.show(); } public static void main(String[] args) { ShapeTest.draw(new Rect(1,2,3,4); ShapeTest.draw(new Circle(5,6,7)); } }
抽象
抽象方法和抽象类的概念
抽象方法的概念
-
抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体
-
具体格式如下:
访问权限 abstract 返回值类型 方法名(形参列表); //抽象方法没有方法体
如:
public abstract void cry();
抽象类的概念
- 抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象
- 抽象类虽然不能创建对象,但是还是有构造方法,其原因是继承的子类可以继承其构造方法使用:super()
抽象类和抽象方法之间的关系
- 抽象类中可以有成员变量、构造方法、成员方法
- 抽象类中可以没有抽象方法,也可以有抽象方法
- 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有抽象方法并且使用abstract关键字修饰的类(避免程序员去调用抽象方法,连类都要求抽象了—>java是一门安全的语言)
抽象类的实际意义
- 抽象类的实际意义不在于创建对象而在于被继承
- 继承抽象类的子类只有两种选择:要么必须也是抽象类,要么实现抽象类中的抽象方法–>也就是说抽象类对子类具有强制性和规范性,因此叫做模板设计模式
抽象类案例
银行有定期账户和活期账户,继承自账户类。账户类中有本金private int money;
和利息计算抽象方法public abstract double getLiXi();
抽象父类:Account类
public abstract class Account {
private int money;
public Account(int money) {
this.money = money;
}
public Account() {
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
if (money >= 0) {
this.money = money;
} else {
System.out.println("输入的本金有误哦!");
}
}
public abstract double getLiXi();
}
继承抽象的子类:FixedAccount类
public class FixedAccount extends Account {
public FixedAccount() {
}
//虽然抽象父类不能创建对象,但是构造方法还是应该有,因为可以让继承类用super()使用
public FixedAccount(int money) {
super(money);
}
@Override
public double getLiXi() {
//利息 = 本金 * 利率 * 时间
return getMoney() * 0.03 * 1;
}
public static void main(String[] args) {
//使用多态:可以提高代码的可维护性和可扩展性
Account account = new FixedAccount(1000);
double rem = account.getLiXi();
System.out.println("1000元获得的利息是:" + rem);
}
}
笔试考点
- private和abstract关键字不能修饰同一个方法:因为abstract修饰的抽象方法必须被子类继承进行方法重写,而private修饰的成员方法不能被继承,会造成冲突
- final和abstract关键字不能修饰同一个方法(或类):final修饰的方法不能被重写,abstract修饰的方法必须被重写,矛盾
- static和abstract关键字不能修饰同一个方法:static关键字修饰一个方法将会使这个方法提升为“类层级”,该方法可以直接用类名.调用,而抽象修饰方法与类目的就是为了不能直接调用,矛盾
多态和抽象开发经验
- 在以后的开发中推荐使用多态的格式,此时父类类型引用直接调用的所有方法一定是父类中拥有的方法,若以后更换子类时,只需要将new关键字后面的子类类型修改而其他地方无需改变就可以立即生效,从而提高了代码的可维护性和可扩展性
- 该方式的缺点是:父类引用不能直接调用子类独有的方法,若调用则需要引用类型的强制类型转换
接口
接口的基本概念和特性
- 接口就是一种比抽象类还抽象的类,体现在接口中所有的方法都为抽象方法并且只能有常量
- 定义类的关键字是class,而定义接口的关键字是interface
- 从Java9开始接口中允许private修饰的方法
- 类使用implements关键字表达实现接口的关系;接口与接口之间可以用extends继承(接口只能继承接口)
- 接口虽然不能直接创建对象,但是依旧可以使用多态创建子类对象指向接口引用
接口的实际意义
- 接口弥补了Java不能多继承的不足:一个类可以实现多个接口
接口案例
黄金类既是金属接口,又是货币接口
金属接口
public interface Metal {
public static final int METAL_VALUE = 1; //接口里面只能有常量,public static final可以省略
//自定义抽象方法描述发光的行为
public abstract void shine(); //public abstract可以省略
}
货币接口
public interface Money {
public static final int VALUE = 1000; //接口里面只能有常量,public static final可以省略
//自定义方法实现货币购买行为
public abstract void buy(); //public abstract可以省略
}
黄金类
public class Gold implements Metal, Money {
@Override
public void shine() {
System.out.println("黄金在闪");
}
@Override
public void buy() {
System.out.println("使用黄金付钱");
}
public static void main(String[] args) {
Metal metal = new Gold(); //多态
metal.shine();
Money money = new Gold(); //多态
money.buy();
}
}
类与接口之间的关系
抽象类和接口的主要区别
-
定义抽象类使用的关键字是abstract class,而定义接口的关键字是interface
-
继承抽象类的关键字是extends,而实现接口的关键字是implements
-
继承抽象类支持单继承,而实现接口支持多实现
-
抽象类中可以有构造方法,而接口中不可以有构造方法
-
抽象类中可以有成员变量,而接口中只可以有常量
-
抽象类中可以有成员方法,而接口中只可以有抽象方法
-
抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写(Java8以前的版本)
-
从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法(使用 接口名. 直接调用即可),但非抽象方法需要使用default关键字修饰
-
从Java9开始增加新特性,接口中允许出现私有方法(主要是当该方法仅想要在该接口中使用,不会超过该接口,故而声明为私有private即可)
-
新特性案例
Runner接口
public interface Runner { public abstract void run(); //java9新特性:若需要定义一个方法,该方法仅在该接口中使用,不会超过该接口,可以定义私有方法 private void show() { System.out.println("在以后的开发中尽量减少重复的代码,也就是减少代码的冗余!"); } //java8之后新特性:接口中使用default修饰的非抽象方法,实现类可以自由选择是否重写 public default void show1() { //System.out.println("在以后的开发中尽量减少重复的代码,也就是减少代码的冗余!"); show(); System.out.println("show1方法中:default修饰的非抽象方法仅仅是接口的默认功能,实现类可自由选择是否重写"); } public default void show2() { //System.out.println("在以后的开发中尽量减少重复的代码,也就是减少代码的冗余!"); show(); System.out.println("show2方法中:default修饰的非抽象方法仅仅是接口的默认功能,实现类可自由选择是否重写"); } //java8新特性:可以在接口中定义实现静态方法:直接用接口名.调用即可 public static void test() { System.out.println("这里是静态方法,可以直接通过接口名.的方式调用,省略对象的创建"); } }
Hunter接口
public interface Hunter extends Runner { public abstract void hunt(); }
Man实现类
public class Man implements Hunter { @Override public void hunt() { System.out.println("狂追小白兔....."); } @Override public void run() { System.out.println("遇到大灰熊,狂奔....."); } public static void main(String[] args) { Runner runner = new Man(); runner.run(); Hunter hunter = new Man(); hunter.hunt(); Runner.test(); } }
总结
-
多态(重中之重)
基本概念、语法格式、多态的特点、引用数据类型之间的类型转换、instanceof、实际意义等
-
抽象类(重点)
抽象方法、抽象类、抽象类和抽象方法的关系、实际意义等
-
接口(重点)
基本概念、常量、抽象方法、弥补不能多继承的不足、接口和类之间的关系、抽象类和接口的主要区别等
特殊类
内部类
内部类的基本概念
- 当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)
- 类中的内容:成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类
实际作用
- 当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以方便地访问外部类地私有成员而不再需要提供公有的get和set方法
内部类的分类
- 普通内部类:直接将一个类的定义放在另外一个类的类体中
- 静态内部类:使用static关键字修饰的内部类,隶属于类层级
- 局部内部类:直接将一个类的定义放在方法体的内部时
- 匿名内部类:就是指没有名字的内部类
普通(成员)内部类
语法规则
访问修饰符 class 外部类类名 {
访问修饰符 class 内部类类名 {
内部类的类体;
}
}
使用方式
-
普通内部类需要使用外部类对象来创建对象
OuterTest outer = new OuterTest(); //创建外部类对象 OuterTest.InnerTest inner = outer.new InnerTest(); //使用外部类对象创建内部类对象
-
普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等
-
普通内部类和普通类一样可以使用final或者abstract关键字修饰
-
普通内部类还可以使用private或protected关键字进行修饰(使用private修饰不能在类的外面创建对象并访问)
-
若内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字
//普通内部类有同名成员变量时的访问情况 public class OuterClass { private int cnt = 0; public class InnerClass { private int cnt = 1; private int ia = 5; public InnerClass(int cnt) { this.cnt = cnt; System.out.println("普通内部类的有参构造方法执行到了!"); } public InnerClass() { System.out.println("普通内部类的无参构造方法执行到了!"); } public void show() { System.out.println("普通内部类的show()方法"); } public void show1(int cnt) { System.out.println("访问普通内部类不同名的成员变量:" + ia); //5 System.out.println("访问方法参数:(就近原则)" + cnt); //就近原则:输出传入的实 //参值 System.out.println("访问普通内部类的同名成员变量:" + this.cnt); //1 普通内部类 //的成员变量值 System.out.println("访问外部类中的同名成员变量:" + OuterClass.this.cnt); //0 //外部类的同名成员变量值 } } }
静态内部类
格式
访问修饰符 class 外部类的类名 {
访问修饰符 static class 内部类的类名 {
内部类的类体;
}
}
使用方式
-
静态内部类不能直接访问外部类的非静态成员
-
静态内部类可以直接创建对象(不再需要先创建外部类对象才能创建)
StaticOuter.StaticInner staticInner = new StaticOuter.StaticInner();
-
若静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用 类名. 的方式访问(静态成员,因为静态内部类不能访问外部类的非静态成员)
-
总结:静态内部类的使用一些需要注意的点与static静态使用注意点区别不大,需要联合起来看
public class StaticOuter { private int cnt = 1; private static int snt = 2; /** * 定义静态内部类 有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("输出静态内部类中的静态成员变量(使用类名直接调用即可):" + StaticInner.snt); //4 System.out.println("静态内部类中不可以调用外部类的非静态成员变量:"/* + cnt */); //Error:Non-static field 'cnt' cannot be referenced from a static context } public void show1(int snt) { System.out.println("就近原则输出方法参数:" + snt); //6 System.out.println("输出外部类的同名成员变量(用 类名. 即可)" + StaticOuter.snt); //2 System.out.println("输出静态内部类的同名成员变量(用 类名. 即可)" + StaticInner.snt); //4 //静态内部类想要调用外部类的show方法只能是通过创建对象调用 new StaticOuter().show(); } } }
局部内部类
格式
访问修饰符 class 外部类的类名 {
访问修饰符 返回值类型 成员方法名(形参列表) {
class 内部类的类名 { //局部内部类不允许有访问修饰符
内部类的类体;
}
}
}
使用方式
- 局部内部类只能在方法体内直接创建对象
- 局部内部类只能在该方法的内部使用
- 局部内部类不能使用访问控制符和static关键字修饰符(原因:局部变量不能也不需要用访问控制符修饰)
- 局部内部类可以访问(只能访问不能重新赋值)所在方法体的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致(从Java8开始不加final关键字默认是final,但是建议加上,因为这样可以避免在局部内部类中修改导致报错)
使用举例
package com.lagou.task10;
public class AreaOuter {
private int cnt = 1;
public void show() {
//定义一个局部变量进行测试,从Java8开始默认是final修饰的变量
//虽然可以省略final关键字,但是还是建议加上
int ic = 4;
//定义局部内部类,只在方法体内好使
class AreaInner {
private int ia = 2;
public AreaInner() {
System.out.println("局部内部类的无参构造方法被调用啦!");
}
public AreaInner(int ia) {
this.ia = ia;
System.out.println("局部内部类的有参构造方法被调用啦");
}
//后面还可以接着定义使用set()和get()方法
public void test() {
int ib = 3;
System.out.println("访问局部内部类中的私有成员变量ia=" + ia);
System.out.println("访问外部类的私有成员变量cnt=" + cnt);
// ic = 5; //报错:ic从Java8开始默认是final修饰的,局部内部类中只可以访问,不能修改
System.out.println("访问局部内部类所在方法体中的局部变量ic=" + ic); //可以直接访问
}
}
//使用局部内部类step1、在方法体中创建对象后再访问局部内部类的成员
AreaInner areaInner = new AreaInner();
areaInner.test();
/*ic = 5; //这样会报错:从内部类引用的变量必须是最终变量或者实际上的最终变量
System.out.println("ic = " + ic);*/
}
}
package com.lagou.task10;
public class AreaOuterTest {
public static void main(String[] args) {
//使用局部内部类步骤step2、在main()方法中创建外部类对象访问对应方法体
AreaOuter areaOuter = new AreaOuter();
areaOuter.show();
}
}
回调模式的概念和编程
回调模式概念
-
回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用到参数对象中所实现(重写)的方法(接口中定义的)
说人话:方法形参可以是接口,但是传入的实参必须是接口实现类对象–>方法内调用该对象的方法调用的是实现类重写的方法
匿名内部类(重点)
匿名内部类的意义
当接口/类类型的引用作为方法的形参时(回调模式),实参的传递方式有两种:
- 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递(当该实现类只是为了一两次作为实参传递就去写一个实现类,岂不是杀鸡用牛刀?—>这时用匿名内部类,而且匿名内部类实现完之后就会进行回收,节省内存空间)
- 使用匿名内部类的语法格式得到接口/类类型的引用即可
匿名内部类的语法格式
接口/父类类型 引用变量名 = new 接口/父类类型() {方法的重写};
Lamda表达式
从Java8开始提出新特性Lamda表达式,可以简化匿名内部类的代码,格式为:
(参数列表) -> {方法体}
案例
接口
public interface AnonymousInterface {
public abstract void show();
}
接口实现类
public class AnonymousInterfaceImpl implements AnonymousInterface {
@Override
public void show() {
System.out.println("接口实现类重写的show()方法");
}
}
测试类
public class AnonymousInterfaceTest {
public static void show(AnonymousInterface anonymousInterface) { //接口可以作为形参,但实参必须是实现类对象
anonymousInterface.show();
}
public static void main(String[] args) {
// AnonymousInterfaceTest.show(new AnonymousInterface()); //Error:接口不能实例化
//方法一:先创建一个接口的实现类后再创建对象作为实参
AnonymousInterfaceTest.show(new AnonymousInterfaceImpl()); //接口类型的引用指向实现类型的对象,形成了多态
System.out.println("-----------------------------------------");
//方法二:使用匿名内部类来得到接口类型的引用
AnonymousInterface anonymousInterface = new AnonymousInterface() {
@Override
public void show() {
System.out.println("匿名内部类重写的show()方法");
}
};
AnonymousInterfaceTest.show(anonymousInterface); //传入的是匿名内部类实现的对象
System.out.println("-----------------------------------------------");
//方式三:使用Lamda表达式
AnonymousInterface anonymousInterface1 = () -> {
System.out.println("Lamda表达式原来如此简单");
};
AnonymousInterfaceTest.show(anonymousInterface1);
}
}
枚举类
枚举类的基本概念
- 一年中的所有季节:春、夏、秋、冬
- 所有的性别:男、女
- 键盘上所有的方向键:上、下、左、右
- 在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有制都可以一一列举出来,而这个列举出来的类型就叫做枚举类型(枚举:即一一列举之意–>可以看成一种特殊的类)
自定义实现枚举类
- 自定义枚举类step1、私有化构造方法,使得该方法只能在本类内部中使用,不能随意创建本类对象
- 自定义枚举类step2、声明本类类型的引用指向本类类型的对象
枚举类型的定义
- 使用public static final表示的常量描述较为繁琐(或造成代码冗余),使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型(命名规范:枚举类型类一般最后要写上Enum)
- 写枚举类有要求两点
- 枚举值本质就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此需要采用
枚举类类名.
的方式调用 - 枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默认也是私有的(不写上修饰符也是默认该构造方法是私有的)
- 枚举值本质就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此需要采用
案例
自定义实现方向枚举类
/**
* 自定义实现枚举类:与单例设计模式实现过程类似
*/
public class Direction {
private final String desc; //用于描述方向字符串的成员变量
//自定义枚举类step2、声明本类类型的引用指向本类类型的对象
//通过以上两步,在外部类想要创建对象只能获得以下四个方向-->实现了枚举
//使用public不太严谨,最好用private;static是为了提升到类层级;final是为了不能再做修改
public static final Direction UP = new Direction("向上");
public static final Direction DOWN = new Direction("向下");
public static final Direction LEFT = new Direction("向左");
public static final Direction RIGHT = new Direction("向右");
//使用构造函数初始化final修饰的成员变量
//自定义枚举类step1、私有化构造方法,使得该方法只能在本类内部中使用,不能随意创建本类对象
private Direction(String desc) {
this.desc = desc;
}
//定义get()方法获取final修饰的成员变量,不需要set()方法,因为final修饰成员变量初始化后不能改变
public String getDesc() {
return desc;
}
}
定义枚举类
/**
* 定义方向枚举类型
*/
public enum DirectionEnum {
//枚举值必须放在最前面,否则会报错-->枚举值本质是一个个public static final修饰的枚举类对象
UP("向上"), DOWN("向下"), LEFT("向左"), RIGHT("向右"); //()中是本类对象-->和自定义枚举类对比下
private final String desc;
DirectionEnum(String desc) {
this.desc = desc;
//构造方法是私有的,在枚举值中使用(被隐藏),外部类是不会调用的,因此这句话不会打印出来
System.out.println("使用枚举类创建对象");
}
public String getDesc() {
return desc;
}
}
测试类
public class DirectionTest {
public static void main(String[] args) {
//若没有私有化构造方法便可以随意创建对象,如何把类限制在上下左右四个方向,谈何枚举?
//Direction direction = new Direction("向前");
//自定义枚举类的使用
Direction d1 = Direction.UP;
System.out.println(d1.getDesc());
Direction d2 = Direction.DOWN;
System.out.println(d2.getDesc());
Direction d3 = Direction.LEFT;
System.out.println(d3.getDesc());
Direction d4 = Direction.RIGHT;
System.out.println(d4.getDesc());
System.out.println("--------------------------------------------------------");
//直接使用枚举类
Direction direction = Direction.UP;
System.out.println(direction.getDesc());
}
}
自定义类和枚举类在switch结构中的使用
public class DirectionUse {
//使用自定义枚举类实现switch结构
public static void test1(/*Direction direction*/String string) {
/*switch (direction) { //Error:switch()中只能是基本数据类型+String+enum类型
case Direction.UP :
System.out.println("举头望明月");
}*/
switch (string) {
case "向上":
System.out.println("举头望明月"); break;
case "向下":
System.out.println("低头思故乡"); break;
case "向左":
System.out.println("左牵黄"); break;
case "向右":
System.out.println("右擎苍"); break;
default:
System.out.println("这个不是正确的方向哦!");
}
}
//使用枚举类实现switch结构
public static void test2(DirectionEnum directionEnum) {
switch (directionEnum) { //switch中可以直接放枚举类型
case UP:
System.out.println("举头望明月"); break;
case DOWN:
System.out.println("低头思故乡"); break;
case LEFT:
System.out.println("左牵黄"); break;
case RIGHT:
System.out.println("右擎苍"); break;
default:
System.out.println("不是正确的方向哦!");
}
}
public static void main(String[] args) {
//自定义枚举类实现switch结构
DirectionUse.test1(Direction.UP.getDesc());
//自定义枚举类实现switch结构巨大缺点:能够传入其他内容字符串!-->能传其他,不像枚举了!
DirectionUse.test1("今天是个好日子");
System.out.println("------------------------------------------------");
//使用枚举类实现switch结构:除了枚举类型值不能再乱传其他东西了-->真. 枚举!
DirectionUse.test2(DirectionEnum.UP); //直接传入枚举类型值
}
}
Enum类的概念和方法
-
所有枚举类都继承自java.lang.Enum,常用方法如下
- static T[] values() 返回当前枚举类中的所有对象
- String toString() 返回当前枚举类对象的名称(当直接打印引用变量时,会自动调用toString()方法)
- int ordinal() 获取枚举对象在枚举类中的索引位置
- static T valueOf(String str) 将参数指定的字符串名转为当前枚举类的对象(注意参数是枚举值的名字,否则会发生非法参数异常IlegalArgumentException)
- int compareTo(E o) 比较两个枚举对象在定义时的顺序(正负零判断:调用的枚举对象位置 - 参数枚举值o的位置)
-
案例
/** * Enum类的常用方法 */ public class DirectionEnumTest { public static void main(String[] args) { //获取全部枚举值并放于数组中 DirectionEnum[] de = DirectionEnum.values(); //遍历获取全部枚举值 for (int i = 0; i < de.length; i++) { System.out.println("枚举值的名称是:" + de[i].toString()); //获取当前枚举值的名称 System.out.println("直接输出枚举值默认调用toString()方法:" + de[i]); System.out.println("当前枚举值的位置是:" + de[i].ordinal()); //获取当前枚举值的索引位置 } //将指定的字符串名字转换为对应的枚举类对象 //DirectionEnum down = DirectionEnum.valueOf("向下"); //编译时正常,运行时IlegalArgumentException DirectionEnum down = DirectionEnum.valueOf("DOWN"); //注意字符串内传的是枚举值的名字,否则会报非法参数异常 System.out.println("当前枚举值名称是:" + down.toString()); //4、使用获取到的枚举对象与枚举类中的已有对象进行先后顺序比较 for (int i = 0; i < de.length; i++) { //正负零判断:调用的枚举对象位置(down) - 参数枚举值(de[i])的位置 System.out.println("获取到的枚举对象与当前枚举类中对象的先后顺序是:" + down.compareTo(de[i])); } } }
枚举类实现接口的方式
枚举类实现接口后需要重写抽象方法,而重写方法的方式有两种:
- 重写一个,所有对象调用同一个(“以前的方法”)
- 每个对象(枚举值)都重写(这样每个枚举值都可以调用自己重写的方法)
public enum DirectionEnum implements DirectionInterface{
//枚举值必须放在最前面,否则会报错-->枚举值本质是一个个public static final修饰的枚举类对象
//()中是本类对象-->和自定义枚举类对比下
//每个枚举值重写:匿名内部类-->接口/父类类型 引用变量名 = new 接口/父类类型() {方法重写}
//public static final Direction UP = new Direction("向上") {方法重写};
UP("向上") {
@Override
public void show() {
System.out.println("贪吃蛇向上了!");
}
}, DOWN("向下") {
@Override
public void show() {
System.out.println("贪吃蛇向下了!");
}
}, LEFT("向左") {
@Override
public void show() {
System.out.println("贪吃蛇向左了!");
}
}, RIGHT("向右") {
@Override
public void show() {
System.out.println("贪吃蛇向右了!");
}
};
private final String desc;
DirectionEnum(String desc) {
this.desc = desc;
//构造方法是私有的,在枚举值中使用(被隐藏),外部类是不会调用的,因此这句话不会打印出来
System.out.println("枚举类对象的构造方法被调用啦");
}
//重写一个方法,所有对象调用同一个重写方法
/*@Override
public void show() {
System.out.println("枚举类中重写的同一个方法");
}*/
}
注解
注解的概念
- 注解(Annotation)又叫标注,使用Java5开始增加的一种引用数据类型–>注解可以看成是一种特殊的接口
- 注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载以及运行时执行指定的处理(比如:@Override若不是重写方法就会报错)
注解的定义和使用
注解的语法格式
-
访问修饰符 @interface 注解名称 { 注解成员; }
-
自定义注解自动继承java.lang.annotation.Annotation接口
-
通过@注解名称的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等
-
若一个注解内没有任何的成员,则这样的注解叫做标记注解/标识注解(仅仅起到标识的作用)
注解的使用方式
-
注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
-
若注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型以及Annotation类型
-
使用注解时要进行赋值,采用成员参数名 = 成员参数值, …;若要设置默认值,在定义注解时的注解体后面添加 default 默认值即可,如:
使用注解时要赋值举例:
@MyAnnotation(value = "值", value2 = "值2")
设置注解默认值举例:
public String value() default "默认值";
元注解
元注解的概念
- 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其他的注解上面(其实就是Java官方给我们写好的注解)
- 元注解主要有@Retention、@Documented、@Target、@Inherited、@Repeatable
元注解@Retention
-
元注解@Retention应用到一个注解上用于说明该注解的生命周期,取值如下:
-
RetentionPolicy.SOURCE注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
-
RetentionPolicy.CLASS注解只被保留到编译进行的时候,它并不会被加载到JVM中<—默认方式
-
RetentionPolicy.RUNTIME注解可以保留到程序运行的时候,它会被加载进入到JVM中,所以在程序运行时可以获取到它们
举例:
@Retention(RetentionPolicy.SOURCE) //表示下面的注解只在源代码阶段有效
-
元注解@Documented
-
使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容
IDEA如何生成API文档:Tools–>Generate JavaDoc–>设置如下图–>Ok
-
@Documented用于指定标识该注解将被javadoc工具提取成文档(因为javadoc工具默认不会抽取注解)
-
定义为@Documented的注解必须设置Retention值为RUNTIME
元注解@Target
- @Target用于指定被修饰得注解能用于哪些元素的修饰,取值如下:
- ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
- ElementType.CONSTRUCTOR 可以给构造方法进行注解
- ElementType.FIELD 可以给属性进行注解
- ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
- ElementType.METHOD 可以给方法进行注解
- ElementType.PACKAGE 可以给一个包进行注解
- ElementType.PARAMETER 可以给一个方法内的参数进行注解
- ElementType.TYPE 可以给类型进行注解,比如类、接口、枚举
- 从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:
- 其中ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型
- 其中ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中
- value值要写多个时的格式是:@Target(value = {值1, 值2, 值3, …}) //记得要加{}
元注解@Inherited
- @Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就该继承超类的注解
元注解@Repeatable
-
@Repeatable表示自然可重复的含义,从Java8开始增加的新特性
-
使用@Repeatable前必须再定义一个可以存放注解数组的注解,然后在将@Repeatable的value值赋值为该注解.class类型
-
举例
step1、定义一个注解ManType(完成step2后对该注解用@Repeatable进行注解)
package com.lagou.task10; import java.lang.annotation.Repeatable; @Repeatable(value = ManTypes.class) step2、将注解数组的.class类型赋值给@Repeatable的value值 public @interface ManType { String value() default ""; //设置默认值为空 }
step2、定义一个step1注解的数组ManTypes,之后将其的.class类型赋值给ManType中的@Repeatable的value值中
package com.lagou.task10; public @interface ManTypes { ManType[] value(); //step1、想要用@Repeatable注解必须先设置一个可以存注解的数组 }
step3、可以对该注解进行重复使用了
package com.lagou.task10; @ManType(value = "工人") //step3、声明完@Repeatable注解后就可以使用重复的注解了 @ManType(value = "教师") //Error:不能有重复的注解 public class Man { }
案例
自定义注解的实现(使用了一些元注解对其进行注解)
package com.lagou.task10;
import java.lang.annotation.*;
//@Retention(RetentionPolicy.SOURCE) //只在源代码阶段保留
@Documented //让该注解可以在javadoc工具中抽取出来
@Retention(RetentionPolicy.RUNTIME) //定义为@Documented的注解必须设置Retention值为RUNTIME
//让该注解可以给构造方法、属性、方法和类型进行注解
@Target(value = {ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Inherited //表示该自定义注解@MyAnontation在使用时能被子类继承
public @interface MyAnontation {
String value() default "默认值"; //给value值设置默认值
}
实现自定义注解之后的使用(在一个类中使用)
package com.lagou.task10;
@MyAnontation(value = "注解的值")
public class Person {
@MyAnontation(value = "给成员变量注解")
private int age;
private String name;
@MyAnontation(value = "给构造方法注解")
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@MyAnontation(value = "给成员方法注解")
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
其他常见预制注解
- 预制注解就是Java语言自身提供的注解,具体如下:
- @author 标明开发该类模板的作者,多个作者之间使用 , 分割
- @version 标明该类模板的版本
- @see 参考转向,也就是相关主题
- @since 从哪个版本开始增加的
- @param 对方法中某参数的说明,若没有参数就不能写
- @return 对方法返回值的说明,若方法的返回值类型是void就不能写
- @exception 对方法可能抛出的异常进行说明
- @Override 限定重写父类方法,该注解只能用于方法
- @Deprecated 用于表示所修饰的元素(类,方法等已过时)
- @SuppressWarnings 抑制编译器警告
总结
-
内部类
概念、普通内部类、静态内部类、局部内部类、匿名内部类、回调模式等
-
枚举类型
概念、自定义枚举类、enum关键字、继承Enum类、实现接口等
-
注解
概念、自定义注解、使用、元注解、预制注解等