(笔记范围:面向对象、成员方法、构造方法、、重载、封装、继承、多态、抽象类、final关键字、接口、内部类、枚举)
一、面向对象编程
•万物皆对象。
•面向对象指以属性和行为的观点去分析现实生活中的事物。
•面向对象编程指先以面向对象的思想进行分析,然后使用面向对象的编程语言进行表达的过程。
类和对象的概念
•对象主要指现实生活中客观存在的实体,在Java语言中对象体现为内存空间中的一块存储区域。
•类简单来就是“分类”,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性的成员变量以及描述行为的成员方法。
•类是用于构建对象的模板,对象的数据结构由定义它的类来决定。
类的定义
•class 类名{
类体;
}
•注意:
通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写。
成员变量的定义
•class 类名{
数据类型成员变量名= 初始值;
}
•注意:
当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首字母大写。
class Person {
String name;
}
对象的创建
•new 类名(new Person()😉;
•注意:
a.当一个类定义完毕后,可以使用new关键字来创建该类的对象,这个
过程叫做类的实例化。
b.创建对象的本质就是在内存空间的堆区申请一块存储区域,用于存放
该对象独有特征信息。
引用的定义
•基本概念
a.使用引用数据类型定义的变量叫做引用型变量,简称为"引用"。
b.引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问。
•语法格式
类名引用变量名;
引用变量名.成员变量名;
Person p = new Person();
p.name = “张飞”;
System.out.println(p.name);
成员变量的初始值
二、成员方法
返回值类型的详解
•返回值主要指从方法体内返回到方法体外的数据内容。
•返回值类型主要指返回值的数据类型,可以是基本数据类型,也可以是引用数据类型。
•当返回的数据内容是66时,则返回值类型写int即可
•在方法体中使用return关键字可以返回具体的数据内容并结束当前方法。
•当返回的数据内容是66时,则方法体中写return 66; 即可
•当该方法不需要返回任何数据内容时,则返回值类型写void即可。
形参列表的详解
•形式参数主要用于将方法体外的数据内容带入到方法体内部。
•形式参数列表主要指多个形式参数组成的列表,语法格式如下:
数据类型形参变量名1, 数据类型形参变量名2, …
•当带入的数据内容是"hello"时,则形参列表写String s 即可
•当带入的数据内容是66和"hello"时,则形参列表写inti, String s 即可
•若该方法不需要带入任何数据内容时,则形参列表位置啥也不写即可。
方法体的详解
•成员方法体主要用于编写描述该方法功能的语句块。
•成员方法可以实现代码的重用,简化代码。
方法的调用
•引用变量名.成员方法名(实参列表);
•实际参数列表主要用于对形式参数列表进行初始化操作,因此参数的个数、类型以及顺序都要完全一致。
•实际参数可以传递直接量、变量、表达式、方法的调用等。
p.show();
可变长参数
•返回值类型方法名(参数的类型… 参数名)
•方法参数部分指定类型的参数个数是可以改变的,也就是0~n个。
•一个方法的形参列表中最多只能声明一个可变长形参,并且需要放到参数列表的末尾。
方法的传参过程
•intmax(intia, intib) { … … … } inta = 5; intb=6; intres = m.max(a,b);
- 为main方法中的变量a、b、res分配空间并初始化。
- 调用max方法,为max方法的形参变量ia、ib分配空间。
- 将实参变量的数值赋值到形参变量的内存空间中。
- max方法运行完毕后返回,形参变量空间释放。
- main方法中的res变量得到max方法的返回值。
- main方法结束后释放相关变量的内存空间。
参数传递的注意事项
•基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常不会影响到实参变量的数值,因为两个变量有各自独立的内存空间;
•引用数据类型的变量作为方法的参数传递时,形参变量指向内容的改变会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间
•当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后再改变指定的内容,则通常不会影响到实参变量指向内容的改变,因为两个变量指向不同的内存空间。
内存结构之栈区
•栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开始到结束会有多次方法的调用。
•JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据。
•当某一个方法调用完成后,其对应的栈帧将被清除。
传参的相关概念
•参数分为形参和实参,定义方法时的参数叫形参,调用方法时传递的参数叫实参。
•调用方法时采用值传递把实参传递给形参,方法内部其实是在使用形参。
•所谓值传递就是当参数是基本类型时,传递参数的值,比如传递i=10,真实传参时,把10赋值给了形参。当参数是对象时,传递的是对象的值,也就是把对象的地址赋值给形参。
成员变量:
成员变量定义在类中,在整个类中都可以被访问。
成员变量随着对象的建立而建立,存在于对象所在的堆内存中。
成员变量有默认初始化值。
局部变量:
局部变量只定义在局部范围内,如:函数内,语句内等。
局部变量存在于栈内存中。
作用的范围结束,变量空间会自动释放。
局部变量没有默认初始化值。
成员变量初始化
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。基本数据类型初始化值为0,引用数据类型初始化值为null。
三、构造方法
默认构造方法
•当一个类中没有定义任何构造方法时,编译器会自动添加一个无参空构造构造方法,叫做默认/缺省构造方法,如:Person(){}
•若类中出现了构造方法,则编译器不再提供任何形式的构造方法。
函数名和类名相同。
没有返回值类型。注意:没有返回值类型不等同于void,void也是一种返回值类型。不能使用return关键字返回任何值。
在使用new关键字创建对象之后自动调用。
构造函数的重载
构造函数的重载和普通函数相同,函数名相同,参数列表不同即可。
构造函数的调用
构造函数在new关键字创建对象时调用。
构造函数可以在该类其他构造函数的第一个语句使用this关键字调用。
构造函数的访问权限
在定义构造函数时,如无特殊需要,应使用public关键字修饰构造函数。
在一些特定情况下,我们不想让别人创建该类对象,那么可以使用private修饰构造函数,例如单态设计模式。
特点:
函数名与类名相同
不用定义返回值类型
不可以写return语句
作用:
给对象进行初始化。
注意:
默认构造函数的特点。
多个构造函数是以重载的形式存在的。
对象一建立就会调用与之对应的构造函数。
构造函数的作用:可以用于给对象进行初始化。
构造函数的小细节:
当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。
当在类中自定义了构造函数后,默认的构造函数就没有了。
构造函数和一般函数在写法上有不同。
在运行上也有不同。
构造函数是在对象一建立就运行。给对象初始化。
而一般方法是对象调用才执行,给是对象添加对象具备的功能。
一个对象建立,构造函数只运行一次。
而一般方法可以被该对象调用多次。
什么时候定义构造函数呢?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。
构造代码块{ 。。。}
作用:给对象进行初始化。
对象一建立就运行,而且优先于构造函数执行。
和构造函数的区别:
构造代码块是给所有对象进行统一初始化,
而构造函数是给对应的对象初始化。
构造代码快中定义的是不同对象共性的初始化内容。
构造方法的作用
•使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作。
/*
编程实现Person类的定义
*/
public class Person {
String name; // 用于描述姓名的成员变量
int age; // 用于描述年龄的成员变量
// 自定义构造方法
// String s = "张飞";
// int i = 30;
// String s = "关羽";
// int i = 35;
// 就近原则 懒人原则
Person(String name, int age) {
//System.out.println("我就是自定义的构造方法!");
//name = "张飞";
//age = 30;
this.name = name;
this.age = age;
}
// 自定义无参构造方法
Person() {
}
// 自定义成员方法实现所有特征的打印 隐含着this关键字,this关键字代表当前正在调用的对象
// Person this = p1; this.name = p1.name = 张飞
// Person this = p2; this.name = p2.name = 关羽
void show() {
// 每当打印成员变量的数值时,让年龄增长一岁
//this.grow();
//grow();
//System.out.println("我是" + this.name + ",今年" + this.age + "岁了!");
System.out.println("我是" + name + ",今年" + age + "岁了!");
}
// 自定义成员方法实现年龄增长一岁的行为
void grow() {
age++;
}
// 自定义成员方法实现年龄增长参数指定数值的行为
void grow(int age) {
this.age += age;
}
// 自定义成员方法实现Person类型对象的获取并返回的行为
//String getName(){}
Person getPerson() {
// 返回当前调用对象本身 Person tp = new Person(); return tp;
return this;
}
public static void main(String[] args) {
// 1.声明一个Person类型的引用指向Person类型的对象
// 当类中没有提供构造方法时,则下面调用默认构造方法,若类中提供构造方法后,则下面调用类中提供的版本
Person p1 = new Person("张飞", 30);
// 并打印特征
p1.show(); // null 0 张飞 30
Person p2 = new Person("关羽", 35);
p2.show(); // 关羽 35
Person p3 = new Person();
p3.show(); // null 0
System.out.println("----------------------------------------------------");
// 2.实现重载方法的调用和测试
p1.grow();
p1.show(); // 张飞 31
p1.grow(3);
p1.show(); // 张飞 34
System.out.println("----------------------------------------------------");
// 3.调用成员方法获取对象
Person p4 = p1.getPerson();
System.out.println("p1 = " + p1);
System.out.println("p4 = " + p4);
}
}
四、方法重载
•若方法名称相同,参数列表不同,这样的方法之间构成重载关系(Overload)。
重载的体现形式
•方法重载的主要形式体现在:参数的个数不同、参数的类型不同、参数的顺序不同,与返回值类型和形参变量名无关,但建议返回值类型最好相同。
•判断方法能否构成重载的核心:调用方法时能否加以区分。
重载的实际意义
•方法重载的实际意义在于调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能。
•如:java.io.PrintStream类中的println方法。
/*
编程实现方法重载主要形式的测试
*/
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);
}
}
五、this关键字
this的基本概念
•若在构造方法中出现了this关键字,则代表当前正在构造的对象。
•若在成员方法中出现了this关键字,则代表当前正在调用的对象。
•this关键字本质上就是当前类类型的引用变量。
工作原理
•在构造方法中和成员方法中访问成员变量时,编译器会加上this.的前缀,而this.相当于汉语中"我的",当不同的对象调用同一个方法时,由于调用方法的对象不同导致this关键字不同,从而this.方式访问的结果也就随之不同。
使用方式
•当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前缀,明确要求该变量是成员变量(重中之重)。
•this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以作为方法的返回值(重点)。
•在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法
注意事项
•引用类型变量用于存放对象的地址,可以给引用类型赋值为null,表示不指向任何对象。
•当某个引用类型变量为null时无法对对象实施访问(因为它没有指向任何对象)。此时,如果通过引用访问成员变量或调用方法,会产生NullPointerException异常。
/*
编程实现this关键字的使用
*/
public class ThisTest {
// 自定义构造方法
ThisTest() {
// this代表当前正在构造的对象
System.out.println("构造方法中:this = " + this);
}
// 自定义成员方法
void show() {
// this代表当前正在调用的对象
System.out.println("成员方法中:this = " + this);
}
public static void main(String[] args) {
// 1.声明ThisTest类型的引用指向该类型的对象
ThisTest tt = new ThisTest();
// 2.调用show方法
tt.show();
System.out.println("main方法中:tt = " + tt);
}
}
/*
编程实现Boy类的定义
*/
public class Boy {
String name; // 用于描述姓名的成员变量
// 自定义构造方法
Boy() {
// 调用本类中的有参构造方法
//this("无名");
System.out.println("无参构造方法!");
}
Boy(String name) {
// 调用本类中的无参构造方法
this();
System.out.println("=========有参构造方法!");
this.name = name;
}
// 自定义成员方法实现特征的打印
void show() {
System.out.println("我的名字是:" + name);
}
public static void main(String[] args) {
// 1.使用无参方式构造对象并打印特征
Boy b1 = new Boy();
b1.show(); // null
System.out.println("-----------------------------------");
// 2.使用有参方式构造对象并打印特征
Boy b2 = new Boy("张飞");
b2.show(); // 张飞
System.out.println("-----------------------------------");
// 3.引用变量的数值可以为空
//Boy b3 = null;
//b3.show(); // 编译ok,运行会发生NullPointerException空指针异常 算术异常、数组下标越界异常
Boy b3 = b2;
b3.show(); // 张飞
}
}
/* 编程实现Point类的定义
*/
public class Point {
int x; // 用于描述横坐标的成员变量
int y; // 用于描述纵坐标的成员变量
// 自定义无参构造方法
Point() {}
// 自定义有参构造方法
Point(int x, int y) {
this.x = x;
this.y = y;
}
// 自定义成员方法实现特征的打印
void show() {
System.out.println("横坐标是:" + x + ",纵坐标是:" + y);
}
// 自定义成员方法实现纵坐标减1的行为
void up() {
y--;
}
// 自定义成员方法实现纵坐标减去参数指定数值的行为
void up(int y) {
this.y -= y;
}
public static void main(String[] args) {
// 1.使用无参方式构造对象并打印特征
Point p1 = new Point();
p1.show(); // 0 0
// 2.使用有参方式构造对象并打印特征
Point p2 = new Point(3, 5);
p2.show(); // 3 5
System.out.println("------------------------------------");
// 3.调用重载的成员方法
p2.up();
p2.show(); // 3 4
p2.up(2);
p2.show(); // 3 2
}
}
六、方法递归调用
递归的基本概念
•递归本质就是指在方法体的内部直接或间接调用当前方法自身的形式。
注意事项
•使用递归必须有递归的规律以及退出条件。
•使用递归必须使得问题简单化而不是复杂化。
•若递归影响到程序的执行性能,则使用递推取代之。
/*
编程实现累乘积的计算并打印
*/
public class JieChengTest {
// 自定义成员方法实现将参数n的阶乘计算出来并返回
int show(int n) {
// 递推的方式
/*
int num = 1;
for(int i = 1; i <= n; i++) {
num *= i;
}
return num;
// 递归的方式
// 当n的数值为1时,则阶乘的结果就是1
if(1 == n) return 1;
// 否则阶乘的结果就是 n*(n-1)!
return n*show(n-1);
}
public static void main(String[] args) {
// 1.声明JieChengTest类型的引用指向该类型的对象
JieChengTest jct = new JieChengTest();
// 2.调用方法进行计算并打印
int res = jct.show(5);
System.out.println("最终的计算结果是:" + res); // 120
}
}
七、封装
封装的概念
•通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无论是编译阶段还是运行阶段都不会报错或者给出提示,此时与现实生活不符。
•为了避免上述错误的发生,就需要对成员变量进行密封包装处理,来隐藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装。
封装的实现流程
•私有化成员变量,使用private关键字修饰。
•提供公有的get和set方法,并在方法体中进行合理值的判断。
•在构造方法中调用set方法进行合理值的判断。
private(私有)关键字
private关键字:
是一个权限修饰符。
用于修饰成员(成员变量和成员函数)
被私有化的成员只在本类中有效。
常用之一:
将成员变量私有化,对外提供对应的set ,get 方法对其进行访问。
提高对数据访问的安全性。
private :私有,权限修饰符:用于修饰类中的成员(成员变量,成员函数)。
私有只在本类中有效。
将age私有化以后,类以外即使建立了对象也不能直接访问。
但是人应该有年龄,就需要在Person类中提供对应访问age的方式。
注意:私有仅仅是封装的一种表现形式。
之所以对外提供访问方式,就因为可以在访问方式中加入逻辑判断等语句。
对访问的数据进行操作。提高代码健壮性
/*
编程实现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());
}
}
/*
编程实现学生信息的录入和打印
*/
import java.util.Scanner;
public class StudentTest2 {
public static void main(String[] args) {
// 1.提示用户输入学生的人数并使用变量记录
System.out.println("请输入学生的人数:");
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
// 2.根据学生的人数准备对应的一维数组
// int[] arr = new int[3]; - 表示声明一个长度为3元素类型为int类型的一维数组
// 数组中的每个元素都是int类型,也就是说数组中的每个元素都可以看做是一个int类型的变量,使用整数数据进行初始化 arr[0] = 10;
// 下面的代码是声明一个长度为num元素类型为Student类型的一维数组
// 数组中的每个元素都是Student类型,也就是说数组中的每个元素都可以看做Student类型的变量,arr[0] = new Student();
Student[] arr = new Student[num];
// 3.提示用户输入每个学生的信息(学号 姓名)并记录到一维数组中
for(int i = 0; i < num; i++) {
System.out.println("请输入第" + (i+1) + "个学生的信息(学号 姓名):");
arr[i] = new Student(sc.nextInt(), sc.next());
}
System.out.println("-----------------------------------------------");
// 4.打印所有学生信息
System.out.println("该班级的所有学生信息有:");
for(int i = 0; i < num; i++) {
//System.out.println(arr[i]);
arr[i].show();
}
}
}
八、static关键字
基本概念
•使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关。
•static关键字修饰的成员可以使用引用.的方式访问,但推荐类名.的方式。
static关键字用来修饰类的成员,被这个关键字修饰的成员都和类加载有关。
JVM运行时不会将所有类加载到内存,因为无法确定程序中要使用哪些。类在第一次使用时加载,只加载一次。
静态变量
用static修饰的变量就是静态变量。
静态变量在类加载后就初始化。
静态变量被类的所有实例所共享。
静态变量可以使用 类名.变量名 形式访问。
如果在定义一个类的时候,发现一个成员变量需要被所有实例所共享,那么这个成员变量就需要定义为static的。
静态方法
用static修饰的方法就是静态方法。
静态方法在类加载后就可以使用。
静态方法可以使用 类名.方法名 形式访问。
静态方法不能直接访问外部非静态成员。
因为外部非静态成员必须在类创建对象之后才能使用,而静态方法可以在没创建对象时就使用。
如果要在静态方法内部访问外部非静态成员,需要先创建该类对象,通过对象访问。
静态方法中不能使用this关键字。
因为this是个引用,哪个对象调用方法就引用哪个对象。 而静态方法有可能不是被对象调用的,this无从引用。
如果一个方法不用访问对象的非静态成员,那么就可以定义为静态的,这样使用者就不需要创建对象,直接用类名调用。
静态方法通常是作为工具方法或者一个可以产生对象的方法被声明,目的是为了让调用者更方便的使用,不必创建对象。
8.1、 static特点
1,随着类的加载而加载。
也就说:静态会随着类的消失而消失。说明它的生命周期最长。
2,优先于的对象存在
明确一点:静态是先存在。对象是后存在的。
3,被所有对象所共享
4,可以直接被类名所调用。
实例变量和类变量的区别:
1,存放位置。
类变量随着类的加载而存在于方法区中。
实例变量随着对象的建立而存在于堆内存中。
2,生命周期:
类变量生命周期最长,随着类的消失而消失。
实例变量生命周期随着对象的消失而消失。
静态使用注意事项:
1,静态方法只能访问静态成员。
非静态方法既可以访问静态也可以访问非静态。
2,静态方法中不可以定义this,super关键字。
因为静态优先于对象存在。所以静态方法中不可以出现this。
3,主函数是静态的。
静态有利有弊
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。
可以直接被类名调用。
弊端:生命周期过长。
访问出现局限性。(静态虽好,只能访问静态。)
使用方式
•在非静态成员方法中既能访问非静态的成员又能访问静态的成员。
(成员:成员变量+ 成员方法,静态成员被所有对象共享)
•在静态成员方法中只能访问静态成员不能访问非静态成员。
(成员:成员变量+ 成员方法,因为此时可能还没有创建对象)
•在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用static关键字修饰。(不能滥用static关键字)
/*
编程实现static关键字的使用
*/
public class StaticTest {
private int cnt = 1; // 隶属于对象层级,也就是每个对象都拥有独立的一份
private static int snt = 2; // 隶属于类层级,也就是所有对象都共享同一份
// 自定义非静态的成员方法 需要使用引用.的方式访问
public void show() {
System.out.println("cnt = " + this.cnt); // 1
System.out.println("snt = " + this.snt); // 2 静态成员被所有对象共享,this关键字可以省略
}
// 自定义静态的成员方法 推荐使用类名.的方式访问
public static void test() {
// StaticTest st = new StaticTest();
//System.out.println("cnt = " + cnt); // 1 静态成员方法中没有this关键字,因为是可以通过类名.方式调用的
System.out.println("snt = " + snt); // 2
}
public static void main(String[] args) {
StaticTest st = new StaticTest();
st.show();
System.out.println("--------------------------------");
StaticTest.test();
}
}
8.2、 构造块和静态代码块
•构造块:在类体中直接使用{}括起来的代码块。
•每创建一个对象都会执行一次构造块。
•静态代码块:使用static关键字修饰的构造块。
•静态代码块随着类加载时执行一次。
用static修饰的代码块就是静态代码块。
静态代码块在类加载后执行。
静态代码块和静态方法相同,不能使用外部非静态成员。
静态代码块执行和静态变量的初始化顺序由代码从上到下顺序决定。
静态代码块。
格式:
static
{
静态代码块中的执行语句。
}
特点:随着类的加载而执行,只执行一次,并优先于主函数。
用于给类进行初始化的。
/*
编程实现构造块和静态代码块的使用
*/
public class BlockTest {
// 当需要在执行构造方法体之前做一些准备工作时,则将准备工作的相关代码写在构造块中即可,比如:对成员变量进行的统一初始化操作
{
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();
}
}
8.3、 java main函数
public static void main(String[] args)
主函数:是一个特殊的函数。作为程序的入口,可以被jvm调用。
主函数的定义:
public:代表着该函数访问权限是最大的。
static:代表主函数随着类的加载就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(String[] arr):函数的参数,参数类型是一个数组,该数组中的元素是字符串。字符串类型的数组。
主函数是固定格式的:jvm识别。
jvm在调用主函数时,传入的是new String[0];
/*
编程实现main方法的测试
*/
public class MainTest {
public static void main(String[] args) {
System.out.println("参数数组中元素的个数是:" + args.length);
System.out.println("传递给main方法的实际参数为:");
for(int i = 0; i < args.length; i++) {
System.out.println("下标为" + i + "的形参变量数值为:" + args[i]);
}
}
}
8.4、 对象初始化过程
Person p = new Person(“zhangsan”,20);
该句话都做了什么事情?
1,因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
3,在堆内存中开辟空间,分配内存地址。
4,在堆内存中建立对象的特有属性。并进行默认初始化。
5,对属性进行显示初始化。
6,对对象进行构造代码块初始化。
7,对对象进行对应的构造函数初始化。
8,将内存地址付给栈内存中的p变量。
对象调用方法过程?
静态内部类
用static修饰的内部类就是静态内部类。
静态内部类在类加载后就可以创建对象,无需创建外部类对象。
8.5、 单例设计模式
•在某些特殊场合中,一个类对外提供且只提供一个对象时,这样的类叫做单例类,而设计单例的流程和思想叫做单例设计模式。
单例设计模式的实现流程
•私有化构造方法,使用private关键字修饰。
•声明本类类型的引用指向本类类型的对象,并使用private static关键字共同修饰。
•提供公有的get方法负责将对象返回出去,并使用public static关键字共同修饰。
单例设计模式的实现方式
•单例设计模式的实现方式有两种:饿汉式和懒汉式,在以后的开发中推荐饿汉式。
什么是设计模式
在编程过程中我们经常会遇到一些典型的问题或需要完成某种特定需求,而这些问题和需求前人也曾经遇到过,他们经过大量理论总结和实践验证之后优选出的代码结构、编程风格、以及解决问题的思考方式,这就是设计模式(Design pattern)。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再去思考和摸索。
单态(单例)设计模式
单态设计模式(Singleton pattern)就是要保证在整个程序中某个类只能存在一个对象,这个类不能再创建第二个对象。
想要保证对象唯一。
1,为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象
2,还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
3,为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
这三部怎么用代码体现呢?
1,将构造函数私有化。
2,在类中创建一个本类对象。
3,提供一个方法可以获取到该对象。
对于事物该怎么描述,还怎么描述。
当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。
/*
这个是先初始化对象。
称为:饿汉式。
Single类一进内存,就已经创建好了对象。
class Single
{
private static Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
*/
//对象是方法被调用时,才初始化,也叫做对象的延时加载。成为:懒汉式。
//Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
//记录原则:定义单例,建议使用饿汉式。
- 编程实现Singleton类的封装
*/
public class Singleton {
// 2.声明本类类型的引用指向本类类型的对象,使用private static关键字共同修饰
//private static Singleton sin = new Singleton(); // 饿汉式
private static Singleton sin = null; // 懒汉式
// 1.私有化构造方法,使用private关键字修饰
private Singleton() {}
// 3.提供公有的get方法负责将对象返回出去,使用public static关键字共同修饰
public static Singleton getInstance() {
//return sin;
if(null == sin) {
sin = new Singleton();
}
return sin;
}
}
/*
编程实现Singleton类的测试
*/
public class SingletonTest {
public static void main(String[] args) {
// 1.声明Singleton类型的引用指向该类型的对象
//Singleton s1 = new Singleton();
//Singleton s2 = new Singleton();
//System.out.println(s1 == s2); // 比较变量s1的数值是否与变量s2的数值相等 false
//Singleton.sin = null; 可以使得引用变量无效
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // true
}
}
九、 继承
9.1 继承的概念
•当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需要编写自己独有特征和行为的机制,叫做继承。
•在Java语言中使用extends(扩展)关键字来表示继承关系。
•如:
public class Worker extends Person{} -表示Worker类继承自Person类
其中Person类叫做超类、父类、基类。
其中Worker类叫做派生类、子类、孩子类。
•使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件。
继承的特点
•子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承只是不能直接访问。
•无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法,来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代码super()的效果。
•使用继承必须满足逻辑关系:子类is a 父类,也就是不能滥用继承。
•Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类。
9.2 方法重写的概念
•从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写(Override)。
方法重写的原则
•要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许返回子类类型。
•要求方法的访问权限不能变小,可以相同或者变大。
•要求方法不能抛出更大的异常(异常机制)。
9.2super 关键字
子父类出现后,类成员的特点:
类中成员:
1,变量。
2,函数。
3,构造函数。
1,变量
如果子类中出现非私有的同名成员变量时,
子类要访问本类中的变量,用this
子类要访问父类中的同名变量,用super。
super的使用和this的使用几乎一致。
this代表的是本类对象的引用。
super代表的是父类对象的引用。
9.3 函数覆盖(override)
2,子父类中的函数。
当子类出现和父类一模一样的函数时,
当子类对象调用该函数,会运行子类函数的内容。
如同父类的函数被覆盖一样。
这种情况是函数的另一个特性:重写(覆盖)
当子类继承父类,沿袭了父类的功能,到子类中,
但是子类虽具备该功能,但是功能的内容却和父类不一致,
这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容。
覆盖:
1,子类覆盖父类,必须保证子类权限(方法的修饰符)大于等于父类权限,才可以覆盖,否则编译失败。
2,静态只能覆盖静态。
记住大家:
重载:只看同名函数的参数列表。
重写:子父类方法要一模一样。
3,子父类中的构造函数。
在对子类对象进行初始化时,父类的构造函数也会运行,
那是因为子类的构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();
为什么子类一定要访问父类中的构造函?
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。
所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
注意:super语句一定定义在子类构造函数的第一行。
9.4 子类实例化过程
子类的实例化过程。
结论:
子类的所有的构造函数,默认都会访问父类中空参数的构造函数。
因为子类每一个构造函数内的第一行都有一句隐式super();
当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问父类中的构造函数。
当然:子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。
子类中至少会有一个构造函数会访问父类中的构造函数。
9.5 构造块与静态代码块
•先执行父类的静态代码块,再执行子类的静态代码块。
•执行父类的构造块,执行父类的构造方法体。
•执行子类的构造块,执行子类的构造方法体。
import java.sql.SQLOutput;
public class SuperTest {
{
System.out.println("SuperTest类中的构造块!"); // (2) c
}
static {
System.out.println("SuperTest类中的静态代码块!"); // (1) a
}
public SuperTest() {
System.out.println("SuperTest类中的构造方法体!"); // (3) d
}
public static void main(String[] args) {
// 使用无参方式构造对象
SuperTest st = new SuperTest();
}
}
// 导入java目录中lang目录中System类中的静态成员out 很少使用
import static java.lang.System.out;
public class SubSuperTest extends SuperTest {
{
System.out.println("==========SubSuperTest类中的构造块!"); // (2) e
}
static {
System.out.println("==========SubSuperTest类中的静态代码块!"); // (1) b
}
public SubSuperTest() {
//System.out.println("==========SubSuperTest类中的构造方法体!"); // (3) f
out.println("==========SubSuperTest类中的构造方法体!");
}
public static void main(String[] args) {
// 使用无参方式构造子类的对象
SubSuperTest sst = new SubSuperTest();
}
}
9.6访问控制
注意事项
•public修饰的成员可以在任意位置使用。
•private修饰的成员只能在本类内部使用。
•通常情况下,成员方法都使用public关键字修饰,成员变量都使用private关键字修饰。
/*
编程实现Person类的封装
*/
public class Person {
// 1.私有化成员变量,使用private关键字修饰
private String name;
private int age;
//private boolean gender; // 性别
// 3.在构造方法中调用set方法进行合理值的判断
public Person() {
System.out.println("Person()");
}
public Person(String name, int age) {
System.out.println("Person(String, int)");
setName(name);
setAge(age);
}
// 2.提供公有的get和set方法并在方法体中进行合理值的判断
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age > 0 && age < 150) {
this.age = age;
} else {
System.out.println("年龄不合理哦!!!");
}
}
// 自定义成员方法实现特征的打印
public void show() {
System.out.println("我是" + getName() + ",今年" + getAge() + "岁了!");
}
// 自定义成员方法描述吃饭的行为
public void eat(String food) {
System.out.println(food + "真好吃!");
}
// 自定义成员方法描述娱乐的行为
public void play(String game) {
System.out.println(game + "真好玩!");
}
}
/*
自定义Worker类继承自Person类
*/
public class Worker extends Person {
private int salary;
public Worker() {
super(); // 表示调用父类的无参构造方法,若没有加则编译器自动添加
System.out.println("Worker()");
}
public Worker(String name, int age, int salary) {
super(name, age); // 表示调用父类的有参构造方法
System.out.println("Worker(String, int, int)");
//setName(name);
//setAge(age);
setSalary(salary);
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
if(salary >= 2200) {
this.salary = salary;
} else {
System.out.println("薪水不合理哦!!!");
}
}
// 自定义成员方法描述工作的行为
public void work() {
System.out.println("今天的砖头有点烫手...");
}
// 自定义show方法覆盖从父类中继承的版本
@Override // 标注/注解,用于说明下面的方法是对父类方法的重写,若没有构成重写则编译报错
public void show() {
super.show(); // 表示调用父类的show方法
System.out.println("我的薪水是:" + getSalary());
}
}
9.7.package
•定义类时需要指定类的名称,但如果仅仅将类名作为类的唯一标识,则不可避免的出现命名冲突的问题。这会给组件复用以及团队间的合作造成很大的麻烦!
•在Java语言中,用包(package)的概念来解决命名冲突的问题。
包的定义
•在定义一个类时,除了定义类的名称一般还要指定一个包名,格式如下:
package 包名;
package 包名1.包名2.包名3…包名n;
•为了实现项目管理、解决命名冲突以及权限控制的效果。
定义包的规范
•如果各个公司或开发组织的程序员都随心所欲的命名包名的话,仍然不能从根本上解决命名冲突的问题。因此,在指定包名的时候应该按照一定的规范。
•org.apache.commons.lang.StringUtil
•其中StringUtils是类名而org.apache.commons.lang是多层包名,其含义如下:org.apache表示公司或组织的信息(是这个公司(或组织)域名的反写);common 表示项目的名称信息;lang 表示模块的名称信息。
包的导入
•使用import关键字导入包。
•使用import关键字导入静态成员,从Java5.0开始支持。
十、 final关键字
基本概念
•final本意为"最终的、不可改变的",可以修饰类、成员方法以及成员变量。
使用方式
•final关键字修饰类体现在该类不能被继承。
-主要用于防止滥用继承,如:java.lang.String类等。
•final关键字修饰成员方法体现在该方法不能被重写但可以被继承。
-主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。
•final关键字修饰成员变量体现在该变量必须初始化且不能改变。
-主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等。
final : 最终。作为一个修饰符,
1,可以修饰类,函数,变量。
2,被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
3,被final修饰的方法不可以被复写。
4,被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,有又可以修饰局部变量。
当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读。
而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。
单词间通过_连接。
5,内部类定义在类中的局部位置上是,只能访问该局部被final修饰的局部变量。
常量的概念
•在以后的开发中很少单独使用final关键字来修饰成员变量,通常使用public static final关键字共同修饰成员变量来表达常量的含义,常量的命名规范要求是所有字母都要大写,不同的单词之间采用下划线连。
•public static final double PI = 3.14;
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
}
}
// ctrl+shift+/ 进行选中内容的多行注释 ,再来一次取消注释
// ctrl+/ 进行当前行的单行注释 ,再来一次取消注释
public /*final*/ class FinalClass {
public final void show() {
System.out.println("FinalClass类中的show方法!");
}
十一、 多态
多态的概念
•多态主要指同一种事物表现出来的多种形态。
•饮料:可乐、雪碧、红牛、脉动、…
•宠物:猫、狗、鸟、小强、鱼、…
•人:学生、教师、工人、保安、…
•图形:矩形、圆形、梯形、三角形、…
多态的语法格式
•父类类型引用变量名= new 子类类型();
•如:
Shape sr= new Rect();
sr.show();
11.1 多态的特点
•当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法。
•当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法。
•对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)。
•对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本。
11.2 引用数据类型之间的转换
•引用数据类型之间的转换方式有两种:自动类型转换和强制类型转换。
•自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也叫做向上转型。
•强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也叫做向下转型或显式类型转换。
•引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
•若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运行阶段发生类型转换异常。
•为了避免上述错误的发生,应该在强转之前进行判断,格式如下:
if(引用变量instanceof数据类型)
判断引用变量指向的对象是否为后面的数据类型
多态的实际意义
•多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的效果。
11.3 多态中成员的使用特点
在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。
简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。
在多态中,成员变量的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。
在多态中,静态成员函数的特点:
无论编译和运行,都参考做左边。
public class Shape {
private int x;
private int y;
public Shape() {
}
public Shape(int x, int y) {
setX(x);
setY(y);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public void show() {
System.out.println("横坐标:" + getX() + ",纵坐标:" + getY());
}
// 自定义静态方法
public static void test() {
System.out.println("Shape类中的静态方法!");
}
}
public class Rect extends Shape {
private int len;
private int wid;
public Rect() {
}
public Rect(int x, int y, int len, int wid) {
super(x, y);
setLen(len);
setWid(wid);
}
public int getLen() {
return len;
}
public void setLen(int len) {
if(len > 0) {
this.len = len;
} else {
System.out.println("长度不合理哦!!!");
}
}
public int getWid() {
return wid;
}
public void setWid(int wid) {
if (wid > 0) {
this.wid = wid;
} else {
System.out.println("宽度不合理哦!!!");
}
}
@Override
public void show() {
super.show();
System.out.println("长度是:" + getLen() + ",宽度是:" + getWid());
}
// 自定义静态方法
//@Override Error: 历史原因、不是真正意义上的重写
public static void test() {
System.out.println("---Rect类中的静态方法!");
}
}
public class Circle extends Shape {
private int ir;
public Circle() {
}
public Circle(int x, int y, int ir) {
super(x, y);
setIr(ir);
}
public int getIr() {
return ir;
}
public void setIr(int ir) {
if (ir > 0) {
this.ir = ir;
} else {
System.out.println("半径不合理哦!!!");
}
}
@Override
public void show() {
super.show();
System.out.println("半径是:" + getIr());
}
}
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("强转有风险,操作需谨慎!");
}
}
}
十二、 抽象类
抽象方法的概念
•抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。
•具体格式如下:
访问权限abstract 返回值类型方法名(形参列表);
public abstract void cry();
抽象类的概念
•抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象。
抽象类和抽象方法的关系
•抽象类中可以有成员变量、构造方法、成员方法;
•抽象类中可以没有抽象方法,也可以有抽象方法;
•拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有抽象方法并且使用abstract关键字修饰的类。
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 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();
}
}
public class SubAbstractTest2 extends AbstractTest {
@Override
public void show() {
System.out.println("使用多态方式可以提高代码的可维护性哦!");
}
public void test() {
System.out.println("第二个子类中独有的方法!");
}
}
抽象类的实际意义
•抽象类的实际意义不在于创建对象而在于被继承。
•当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。
public /*final*/ abstract class Account {
private int money;
public Account() {
}
public Account(int money) {
setMoney(money);
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
if (money >= 0) {
this.money = money;
} else {
System.out.println("金额不合理哦!!!");
}
}
// 自定义抽象方法实现计算利息并返回的功能描述
public abstract double getLixi();
// private 和 abstract 关键字不能共同修饰一个方法
//private abstract double getLixi();
// final 和 abstract 关键字不能共同修饰一个方法
//public final abstract double getLixi();
// static 和 abstract 关键字不能共同修饰一个方法
//public static abstract double getLixi();
}
public class FixedAccount extends Account {
public FixedAccount() {
}
public FixedAccount(int i) {
super(i); // 表示调用父类的有参构造方法
}
@Override
public double getLixi() {
// 利息 = 本金 * 利率 * 时间
return getMoney() * 0.03 * 1;
}
public static void main(String[] args) {
// 1.声明Account类型的引用指向子类类型的对象,形成了多态
//Account acc = new FixedAccount(1000);
Account acc = new FixedAccount();
acc.setMoney(1000);
double res = acc.getLixi();
System.out.println("计算的利息是:" + res); // 30.0
}
}
十三、 接口
接口的基本概念
•接口就是一种比抽象类还抽象的类,体现在所有方法都为抽象方法。
•定义类的关键字是class,而定义接口的关键字是interface。
•如:
金属接口货币接口黄金类
public interface Money {
// 自定义抽象方法描述购物的行为
public abstract void buy();
}
public interface Metal {
// 自定义抽象方法描述发光的行为
public abstract void shine();
}
// 使用implements关键字表达实现的关系,支持多实现
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) {
// 1.声明接口类型的引用指向实现类的对象,形成了多态
Metal mt = new Gold();
mt.shine();
Money mn = new Gold();
mn.buy();
}
}
什么是接口
接口是一种特殊的抽象类,接口中声明的所有方法都是抽象的
使用interface关键字修饰一个接口
接口的用法
我们可以定义一个类来实现接口,使用implements关键字
实现一个接口需要实现接口中所有的方法,抽象类除外
通常使用匿名内部类来实现一个接口
接口可以继承接口(多继承),使用extends关键字。 接口不能继承抽象类,因为抽象类中可能有不抽象的方法。
一个类可以实现多个接口,为了实现多态
接口中的方法和变量
接口中定义的方法默认是公有的抽象的,被public abstract修饰
接口中定义的变量默认为全局常量,使用public static final修饰
public interface InterfaceTest {
/*public static final */int CNT = 1; // 里面只能有常量
//private void show(){} // 从Java9开始允许接口中出现私有方法
/*public abstract */void show(); // 里面只能有抽象方法(新特性除外),注释中的关键字可以省略,但建议写上
}
抽象类和接口的主要区别(笔试题)
•定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
•继承抽象类的关键字是extends,而实现接口的关键字是implements。
•继承抽象类支持单继承,而实现接口支持多实现。
•抽象类中可以有构造方法,而接口中不可以有构造方法。
•抽象类中可以有成员变量,而接口中只可以有常量。
•抽象类中可以有成员方法,而接口中只可以有抽象方法。
•抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写(Java8以前的版本)。
•从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非抽象方法需要使用default关键字修饰。
•从Java9开始增加新特性,接口中允许出现私有方法。
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();
}
}
十四、 内部类
内部类的基本概念
•当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)。
•类中的内容:成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类。
实际作用
•当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法。
内部类的分类
•普通内部类-直接将一个类的定义放在另外一个类的类体中。
•静态内部类-使用static关键字修饰的内部类,隶属于类层级。
•局部内部类-直接将一个类的定义放在方法体的内部时。
•匿名内部类-就是指没有名字的内部类。
普通(成员)内部类的格式
•访问修饰符class 外部类的类名{
访问修饰符class 内部类的类名{
内部类的类体;
}
}
普通内部类的使用方式
•普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等。
•普通内部类和普通类一样可以使用final或者abstract关键字修饰。
•普通内部类还可以使用private或protected关键字进行修饰。
•普通内部类需要使用外部类对象来创建对象。
•如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字。
/**
* 编程实现普通内部类的定义和使用 - 文档注释
*/
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 外部类的类名{
访问修饰符staticclass 内部类的类名{
内部类的类体;
}
}
静态内部类的使用方式
•静态内部类不能直接访问外部类的非静态成员。
•静态内部类可以直接创建对象。
•如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。
/**
* 实现静态内部类的定义和使用
*/
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的。由局部内部类和局部变量的声明周期不同所致
/**
* 编程实现局部内部类的定义和使用
*/
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 接口/父类类型() { 方法的重写};
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开始提出新特性lamda表达式可以简化上述代码,格式为:(参数列表) -> {方法体}
AnonymousInterface ait2 = () -> System.out.println("lamda表达式原来是如此简单!");
AnonymousInterfaceTest.test(ait2);
}
}
内部类的访问规则:
1,内部类可以直接访问外部类中的成员,包括私有。
之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this
2,外部类要访问内部类,必须建立内部类对象。
访问格式:
1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中。
可以直接建立内部类对象。
格式
外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer().new Inner();
2,当内部类在成员位置上,就可以被成员修饰符所修饰。
比如,private:将内部类在外部类中进行封装。
static:内部类就具备static的特性。
当内部类被static修饰后,只能直接访问外部类中的static成员。出现了访问局限。
在外部其他类中,如何直接访问static内部类的非静态成员呢?
new Outer.Inner().function();
在外部其他类中,如何直接访问static内部类的静态成员呢?
Outer.Inner.function();
注意:当内部类中定义了静态成员,该内部类必须是static的。
当外部类中的静态方法访问内部类时,内部类也必须是static的。
当描述事物时,事物的内部还有事物,该事物用内部类来描述。
因为内部事务在使用外部事物的内容。
内部类定义在局部时,
1,不可以被成员修饰符修饰
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。
但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
匿名内部类:
1,匿名内部类其实就是内部类的简写格式。
2,定义匿名内部类的前提:
内部类必须是继承一个类或者实现接口。
3,匿名内部类的格式: new 父类或者接口(){定义子类的内容}
4,其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。 可以理解为带内容的对象。
5,匿名内部类中定义的方法最好不要超过3个。
十五、 枚举
枚举的基本概念
•一年中的所有季节:春季、夏季、秋季、冬季。
•所有的性别:男、女。
•键盘上的所有方向按键:向上、向下、向左、向右。
•在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型。
枚举的定义
•使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型。
•枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用枚举类型.的方式调用。
•枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默认也是私有的。
/**
* 编程实现所有方向的枚举,所有的方向:向上、向下、向左、向右
*/
public class Direction {
private final String desc; // 用于描述方向字符串的成员变量
// 2.声明本类类型的引用指向本类类型的对象
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("向右");
// 通过构造方法实现成员变量的初始化,更加灵活
// 1.私有化构造方法,此时该构造方法只能在本类的内部使用
private Direction(String desc) {
this.desc = desc;
}
// 通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
}
public class DirectionTest {
public static void main(String[] args) {
/*// 1.声明Direction类型的引用指向该类型的对象并打印特征
Direction d1 = new Direction("向上");
System.out.println("获取到的字符串是:" + d1.getDesc()); // 向上
Direction d2 = new Direction("向下");
System.out.println("获取到的字符串是:" + d2.getDesc()); // 向下
Direction d3 = new Direction("向左");
System.out.println("获取到的字符串是:" + d3.getDesc()); // 向左
Direction d4 = new Direction("向右");
System.out.println("获取到的字符串是:" + d4.getDesc()); // 向右
System.out.println("-------------------------------------");
Direction d5 = new Direction("向前");
System.out.println("获取到的字符串是:" + d5.getDesc()); // 向前*/
//Direction.UP = 2; Error:类型不匹配
//Direction d2 = null;
//Direction.UP = d2; Error: final关键字修饰
Direction d1 = Direction.UP;
System.out.println("获取到的方向是:" + d1.getDesc()); // 向上
System.out.println("-------------------------------------");
// 使用一下Java5开始的枚举类型
DirectionEnum de = DirectionEnum.DOWN;
System.out.println("获取到的方向是:" + de.getDesc()); // 向下
}
}
Enum类的概念和方法
•所有的枚举类都继承自java.lang.Enum类,常用方法如下:
public class DirectionUseTest {
// 自定义静态方法实现根据参数指定的字符串内容来打印具体的方向信息
public static void test1(String str) {
switch (str) {
case "向上":
System.out.println("抬头望明月!"); break;
case "向下":
System.out.println("低头思故乡!"); break;
case "向左":
System.out.println("左牵黄"); break;
case "向右":
System.out.println("右擎苍"); break;
default:
System.out.println("没有这样的方向哦!");
}
}
// 自定义静态方法实现根据参数指定的枚举类型来打印具体的方向信息
public static void test2(DirectionEnum de) {
switch (de) {
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) {
DirectionUseTest.test1(Direction.UP.getDesc());
DirectionUseTest.test1("今天是个好日子!");
System.out.println("--------------------------------------------");
DirectionUseTest.test2(DirectionEnum.DOWN);
//DirectionUseTest.test2("今天是个好日子!"); Error:类型不匹配,减少了出错的可能性
}
}
枚举类实现接口的方式
•枚举类实现接口后需要重写抽象方法,而重写方法的方式有两种:重写一个,或者每个对象都重写。
public interface DirectionInterface {
// 自定义抽象方法
public abstract void show();
}
/**
* 编程实现所有方向的枚举,所有的方向:向上、向下、向左、向右 枚举类型要求所有枚举值必须放在枚举类型的最前面
*/
public enum DirectionEnum implements DirectionInterface {
// 2.声明本类类型的引用指向本类类型的对象
// 匿名内部类的语法格式:接口/父类类型 引用变量名 = 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; // 用于描述方向字符串的成员变量
// 通过构造方法实现成员变量的初始化,更加灵活
// 1.私有化构造方法,此时该构造方法只能在本类的内部使用
private DirectionEnum(String desc) { this.desc = desc; }
// 通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
// 整个枚举类型只重写一次,所有对象调用同一个
/*@Override
public void show() {
System.out.println("现在可以实现接口中抽象方法的重写了!");
}*/
}
/**
* 编程实现方向枚举类的测试,调用从Enum类中继承下来的方法
*/
public class DirectionEnumTest {
public static void main(String[] args) {
// 1.获取DirectionEnum类型中所有的枚举对象
DirectionEnum[] arr = DirectionEnum.values();
// 2.打印每个枚举对象在枚举类型中的名称和索引位置
for (int i = 0; i < arr.length; i++) {
System.out.println("获取到的枚举对象名称是:" + arr[i].toString());
System.out.println("获取到的枚举对象对应的索引位置是:" + arr[i].ordinal()); // 和数组一样下标从0开始
}
System.out.println("---------------------------------------------------------------");
// 3.根据参数指定的字符串得到枚举类型的对象,也就是将字符串转换为对象
//DirectionEnum de = DirectionEnum.valueOf("向下"); // 编译ok,运行发生IllegalArgumentException非法参数异常
DirectionEnum de = DirectionEnum.valueOf("DOWN");
//DirectionEnum de = DirectionEnum.valueOf("UP LEFT"); // 要求字符串名称必须在枚举对象中存在
//System.out.println("转换出来的枚举对象名称是:" + de.toString());
System.out.println("转换出来的枚举对象名称是:" + de); // 当打印引用变量时,会自动调用toString方法
System.out.println("---------------------------------------------------------------");
// 4.使用获取到的枚举对象与枚举类中已有的对象比较先后顺序
for(int i = 0; i < arr.length; i++) {
// 当调用对象在参数对象之后时,获取到的比较结果为 正数
// 当调用对象在参数对象相同位置时,则获取到的比较结果为 零
// 当调用对象在参数对象之前时,则获取到的比较结果为 负数
System.out.println("调用对象与数组中对象比较的先后顺序结果是:" + de.compareTo(arr[i]));
}
System.out.println("---------------------------------------------------------------");
// 5.使用数组中每个DirectionEnum对象都去调用show方法测试
for (int i = 0; i < arr.length; i++) {
arr[i].show();
}
}
}
十六、 注解
注解的基本概念
•注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型。
•注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理。
注解的语法格式
•访问修饰符@interface 注解名称{
注解成员;
}
•自定义注解自动继承java.lang.annotation.Annotation接口。
•通过@注解名称的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等。
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 类型有要求
public String value() default "123"; // 声明一个String类型的成员变量,名字为value
public String value2();
}
注解的使用方式
•注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
•如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型。
元注解的概念
•元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
•元注解主要有@Retention、@Documented、@Target、@Inherited、@Repeatable。
元注解@Retention
•@Retention 应用到一个注解上用于说明该注解的的生命周期,取值如下:
•RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
•RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM 中,默认方式。
•RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到JVM 中,所以在程序运行时可以获取到它们。
元注解@Documented
•使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容。
•@Documented用于指定被该注解将被javadoc工具提取成文档。
•定义为@Documented的注解必须设置Retention值为RUNTIME。
元注解@Target
•@Target用于指定被修饰的注解能用于哪些元素的修饰,取值如下:
元注解@Inherited
•@Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解。
元注解@Repeatable
•@Repeatable表示自然可重复的含义,从Java8开始增加的新特性。
•从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:
•其中ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型。
•其中ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
/**
* 自定义注解用于描述任务的角色
*/
@Repeatable(value = ManTypes.class)
@Target(ElementType.TYPE_USE)
public @interface ManType {
String value() default "";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 自定义注解里面可以描述多种角色
*/
@Target(ElementType.TYPE_USE)
public @interface ManTypes {
ManType[] value();
}
常见的预制注解
•预制注解就是Java语言自身提供的注解,具体如下:
常见的预制注解
•常用的预制注解如下:
// 表示将标签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;
}
}
// 也就是可以继承Person类的注解
public class Student extends Person {
}
@ManType(value = "职工")
@ManType(value = "超人")
//@ManTypes({@ManType(value = "职工"), @ManType(value = "超人")}) // 在Java8以前处理多个注解的方式
public class Man {
@Deprecated // 表示该方法已经过时,不建议使用
public void show() {
System.out.println("这个方法马上过时了!");
}
public static void main(String[] args) {
int ia = 97;
char c1 = (@ManType char) ia;
}
}
public class ManTest {
public static void main(String[] args) {
Man man = new Man();
man.show();
}
}