1.继承
1.1 继承概念
继承的概念:继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
我们将"继承概念"分为两类:
子类 (Subclass) - 子,从另一个类继承的类
超类 (Superclass) - 父,被继承的类要从类继承,请使用 extends关键字,类的继承格式:
class 父类 {
}
class 子类 extends 父类 {
}为什么要继承类:
- 当我们开发过程中,对于不同的类中,需要用到同样的属性字段时,我们可以使用继承
- 继承可以减少代码的重复性,提高代码的可维护性
我们创建一个学生类,再分别创建小学和高中分别继承学生信息为例:
package my.java.study;
/*学生信息类*/
public class StudentInfo {
private String studentName;//姓名
private String studentNo;//学号
private int studentAge;//年龄
private String studentGender;//性别
/*姓名Set*/
public void setStudentName(String studentName) {
this.studentName = studentName;
}
/*姓名Get*/
public String getStudentName() {
return studentName;
}
/*学号Set*/
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
/*学号Get*/
public String getStudentNo() {
return studentNo;
}
/*年龄Set*/
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
/*年龄Get*/
public int getStudentAge() {
return studentAge;
}
/*性别Set*/
public void setStudentGender(String studentGender) {
this.studentGender = studentGender;
}
/*性别Get*/
public String getStudentGender() {
return studentGender;
}
}
package my.java.study;
/*小学生*/
public class PrimarySchoolStudent extends StudentInfo {
private int CalligraphyNum;//书法课程数
//书法课程数set
public void setCalligraphyNum(int calligraphyNum) {
CalligraphyNum = calligraphyNum;
}
//书法课程数get
public int getCalligraphyNum() {
return CalligraphyNum;
}
}
package my.java.study;
/*高中生*/
public class HighSchoolStudent extends StudentInfo {
private int physicsNum;//物理课程数
private int chemistryNum;//化学课程数
//物理课程数set
public void setPhysicsNum(int physicsNum) {
this.physicsNum = physicsNum;
}
//物理课程数get
public int getPhysicsNum() {
return physicsNum;
}
//化学课程数set
public void setChemistryNum(int chemistryNum) {
this.chemistryNum = chemistryNum;
}
//化学课程数get
public int getChemistryNum() {
return chemistryNum;
}
}
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*继承*/
PrimarySchoolStudent ps = new PrimarySchoolStudent();
ps.setStudentName("王子豪");
ps.setStudentNo("202409008");
ps.setStudentAge(7);
ps.setStudentGender("男");
ps.setCalligraphyNum(2);
System.out.printf("大家好,我是一名小学生,我叫%s,我是%s生,我今年%d岁了,我每周需要上%d节书法课。",
ps.getStudentName(),ps.getStudentGender(), ps.getStudentAge(),ps.getCalligraphyNum());
HighSchoolStudent hs = new HighSchoolStudent();
hs.setStudentName("刘雯");
hs.setStudentNo("2024090080098");
hs.setStudentAge(17);
hs.setStudentGender("女");
hs.setPhysicsNum(5);
hs.setChemistryNum(4);
System.out.printf("大家好,我是一名高中生,我叫%s,我是%s生,我今年%d岁了,我每周需要上%d节物理课和%d节化学课。",
hs.getStudentName(),hs.getStudentGender(), hs.getStudentAge(),hs.getPhysicsNum(),hs.getChemistryNum());
}
}
1.2 继承的类型和特性
继承类型:需要注意的是 Java 不支持多继承,但支持多重继承
继承特性:
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
1.3 继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承 Object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字:类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
implements关键字:使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口
super关键字:我们可以通过 super 关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用,引用当前对象,即它所在的方法或构造函数所属的对象实例。
final关键字:final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写
final声明类:
final class 类名 {//类体}
final声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}注: final 定义的类,其中的属性、方法不是 final 的。
使用implements 实现接口多继承示例:
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
使用super和this关键字示例(ShowStudentInfo方法):
package my.java.study;
/*学生信息类*/
public class StudentInfo {
private String studentName;//姓名
private String studentNo;//学号
private int studentAge;//年龄
private String studentGender;//性别
/*姓名Set*/
public void setStudentName(String studentName) {
this.studentName = studentName;
}
/*姓名Get*/
public String getStudentName() {
return studentName;
}
/*学号Set*/
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
/*学号Get*/
public String getStudentNo() {
return studentNo;
}
/*年龄Set*/
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
/*年龄Get*/
public int getStudentAge() {
return studentAge;
}
/*性别Set*/
public void setStudentGender(String studentGender) {
this.studentGender = studentGender;
}
/*性别Get*/
public String getStudentGender() {
return studentGender;
}
//显示学生信息
public void ShowStudentInfo() {
System.out.println("Student Name: " + studentName);
}
}
package my.java.study;
/*高中生*/
public class HighSchoolStudent extends StudentInfo {
private int physicsNum;//物理课程数
private int chemistryNum;//化学课程数
//物理课程数set
public void setPhysicsNum(int physicsNum) {
this.physicsNum = physicsNum;
}
//物理课程数get
public int getPhysicsNum() {
return physicsNum;
}
//化学课程数set
public void setChemistryNum(int chemistryNum) {
this.chemistryNum = chemistryNum;
}
//化学课程数get
public int getChemistryNum() {
return chemistryNum;
}
//显示学生信息
public void ShowStudentInfo() {
System.out.println("highStudent Name: " + getStudentName());
}
public void ShowTest(){
this.ShowStudentInfo();//调用自己方法
super.ShowStudentInfo();//调用父类方法
}
}
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*继承*/
HighSchoolStudent hs = new HighSchoolStudent();
hs.setStudentName("刘雯");
hs.setStudentNo("2024090080098");
hs.setStudentAge(17);
hs.setStudentGender("女");
hs.setPhysicsNum(5);
hs.setChemistryNum(4);
System.out.printf("大家好,我是一名高中生,我叫%s,我是%s生,我今年%d岁了,我每周需要上%d节物理课和%d节化学课。",
hs.getStudentName(),hs.getStudentGender(), hs.getStudentAge(),hs.getPhysicsNum(),hs.getChemistryNum());
hs.ShowTest();
}
}
1.4 构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
实例代码:
package my.java.study;
/*学生信息类*/
public class StudentInfo {
private String studentName;//姓名
private String studentNo;//学号
private int studentAge;//年龄
private String studentGender;//性别
//无参构造器
public StudentInfo(){
System.out.println("学生你好!");
}
//带参构造器
public StudentInfo(String studentName) {
this.studentName=studentName;
}
/*姓名Set*/
public void setStudentName(String studentName) {
this.studentName = studentName;
}
/*姓名Get*/
public String getStudentName() {
return studentName;
}
/*学号Set*/
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
/*学号Get*/
public String getStudentNo() {
return studentNo;
}
/*年龄Set*/
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
/*年龄Get*/
public int getStudentAge() {
return studentAge;
}
/*性别Set*/
public void setStudentGender(String studentGender) {
this.studentGender = studentGender;
}
/*性别Get*/
public String getStudentGender() {
return studentGender;
}
}
package my.java.study;
/*小学生*/
public class PrimarySchoolStudent extends StudentInfo {
private int CalligraphyNum;//书法课程数
//无参构造器 自动继承 StudentInfo的无参构造函数
public PrimarySchoolStudent() {
}
//书法课程数set
public void setCalligraphyNum(int calligraphyNum) {
CalligraphyNum = calligraphyNum;
}
//书法课程数get
public int getCalligraphyNum() {
return CalligraphyNum;
}
}
package my.java.study;
/*高中生*/
public class HighSchoolStudent extends StudentInfo {
private int physicsNum;//物理课程数
private int chemistryNum;//化学课程数
//带参构造器
public HighSchoolStudent(String studentName){
super(studentName);//调用父类带参构造器
}
//物理课程数set
public void setPhysicsNum(int physicsNum) {
this.physicsNum = physicsNum;
}
//物理课程数get
public int getPhysicsNum() {
return physicsNum;
}
//化学课程数set
public void setChemistryNum(int chemistryNum) {
this.chemistryNum = chemistryNum;
}
//化学课程数get
public int getChemistryNum() {
return chemistryNum;
}
}
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*继承*/
PrimarySchoolStudent ps = new PrimarySchoolStudent();
ps.setStudentName("王子豪");
ps.setStudentNo("202409008");
ps.setStudentAge(7);
ps.setStudentGender("男");
ps.setCalligraphyNum(2);
System.out.printf("大家好,我是一名小学生,我叫%s,我是%s生,我今年%d岁了,我每周需要上%d节书法课。\r\n",
ps.getStudentName(),ps.getStudentGender(), ps.getStudentAge(),ps.getCalligraphyNum());
HighSchoolStudent hs = new HighSchoolStudent("刘雯");
//hs.setStudentName("刘雯");
hs.setStudentNo("2024090080098");
hs.setStudentAge(17);
hs.setStudentGender("女");
hs.setPhysicsNum(5);
hs.setChemistryNum(4);
System.out.printf("大家好,我是一名高中生,我叫%s,我是%s生,我今年%d岁了,我每周需要上%d节物理课和%d节化学课。",
hs.getStudentName(),hs.getStudentGender(), hs.getStudentAge(),hs.getPhysicsNum(),hs.getChemistryNum());
}
}
运行结果:
2.重写/重载
2.1 重写
重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。 即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。这样,在使用子类对象调用该方法时,将执行子类中的方法而不是父类中的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
重写实例(获取学生信息方法):
package my.java.study;
/*学生信息类*/
public class StudentInfo {
private String studentName;//姓名
private String studentNo;//学号
private int studentAge;//年龄
private String studentGender;//性别
/*姓名Set*/
public void setStudentName(String studentName) {
this.studentName = studentName;
}
/*姓名Get*/
public String getStudentName() {
return studentName;
}
/*学号Set*/
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
/*学号Get*/
public String getStudentNo() {
return studentNo;
}
/*年龄Set*/
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
/*年龄Get*/
public int getStudentAge() {
return studentAge;
}
/*性别Set*/
public void setStudentGender(String studentGender) {
this.studentGender = studentGender;
}
/*性别Get*/
public String getStudentGender() {
return studentGender;
}
//获取学生信息
public String GetStudentInfo() {
return studentName + ", " + studentNo + ", " + studentAge + ", " + studentGender;
}
}
package my.java.study;
/*小学生*/
public class PrimarySchoolStudent extends StudentInfo {
private int CalligraphyNum;//书法课程数
//书法课程数set
public void setCalligraphyNum(int calligraphyNum) {
CalligraphyNum = calligraphyNum;
}
//书法课程数get
public int getCalligraphyNum() {
return CalligraphyNum;
}
//重写——获取学生信息
public String GetStudentInfo() {
String str= super.GetStudentInfo();//super 关键字调用父类方法
String str1="我是一名小学生";
str1+="我一周需要上"+CalligraphyNum+"节书法课";
return str+str1;
}
}
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*继承*/
PrimarySchoolStudent ps = new PrimarySchoolStudent();
ps.setStudentName("王子豪");
ps.setStudentNo("202409008");
ps.setStudentAge(7);
ps.setStudentGender("男");
ps.setCalligraphyNum(2);
System.out.printf("大家好,我是一名小学生,我叫%s,我是%s生,我今年%d岁了,我每周需要上%d节书法课。\r\n",
ps.getStudentName(),ps.getStudentGender(), ps.getStudentAge(),ps.getCalligraphyNum());
System.out.println(ps.GetStudentInfo());
}
}
2.2 重载
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
重载实例(计算数字之和方法):
public class Main {
public static void main(String[] args) {
System.out.println("整数之和:"+CalSum(new int[]{1,2,3,4,5}));
System.out.println("小数之和:"+CalSum(new float[]{1.3f,2.54f,3.6f,4.09f,5.5f}));
}
//计算数字之和
public static int CalSum(int...numbers){
int sum = 0;
for(int i : numbers){
sum += i;
}
return sum;
}
//计算小数之和
public static float CalSum(float...numbers){
float sum = 0;
for (float number : numbers) {
sum += number;
}
return sum;
}
}
2.3 重写与重载的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
3.多态
什么是多态
- 多态意味着"多种形式",当我们有许多通过继承相互关联的类时,就会产生多态性,比如我们前面讲到的继承、重写、重载等,还有接口、抽象类和抽象方法等
- 多态是同一个行为具有多个不同表现形式或形态的能力
- 多态就是同一个接口,使用不同的实例而执行不同操作,比如打印机的彩色打印和黑白打印
- 多态性是对象多种表现形式的体现,比如键盘的同一个按键在不同系统中的功能不同
多态的优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
多态存在的必要条件
- 继承
- 重写
- 父类引用指向子类对象:Parent p = new Child();
4.内部类
内部类也称为嵌套类,就是类中的类,嵌套类的目的是将属于同一类的类分组,这使代码更具可读性和可维护性。
内部类可以是
private
私有的或protected
受保护的或者public内部类可以是静态的
内部类可以访问外部类的属性和方法
内部类实例:
package my.java.study;
/*学生信息类*/
public class StudentInfo {
private String studentName;//姓名
private String studentNo;//学号
private int studentAge;//年龄
private String studentGender;//性别
/*姓名Set*/
public void setStudentName(String studentName) {
this.studentName = studentName;
}
/*姓名Get*/
public String getStudentName() {
return studentName;
}
/*学号Set*/
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
/*学号Get*/
public String getStudentNo() {
return studentNo;
}
/*年龄Set*/
public void setStudentAge(int studentAge) {
this.studentAge = studentAge;
}
/*年龄Get*/
public int getStudentAge() {
return studentAge;
}
/*性别Set*/
public void setStudentGender(String studentGender) {
this.studentGender = studentGender;
}
/*性别Get*/
public String getStudentGender() {
return studentGender;
}
//内部类 JuniorStudent
public class JuniorStudent{
private String address;//地址
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
}
}
调用内部类:
import my.java.study.*;
public class Main {
public static void main(String[] args) {
StudentInfo si = new StudentInfo();
StudentInfo.JuniorStudent js=si.new JuniorStudent();
}
}
5.抽象类
数据抽象是隐藏某些细节并仅向用户显示基本信息的过程。
抽象类:是一个不能用于创建对象的受限类(要访问它,必须从另一个类继承)。抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
创建抽象类,使用关键字abstract
抽象类可以有抽象方法和常规方法
抽象方法: 只能在抽象类中使用,并且它没有主体。主体由子类(继承自)提供。定义抽象方法使用关键字abstract。
声明抽象方法会造成以下两个结果:
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象
抽象类及抽象方法实例:
package my.java.study;
//抽象类Employee
public abstract class Employee {
private String emplyeeName;//员工姓名
private String emplyeeTel;//员工电话
private String emplyeeAddress;//员工住址
//员工姓名Set
public void SetEmplyeeName(String emplyeeName) {
this.emplyeeName = emplyeeName;
}
//员工姓名Get
public String getEmplyeeName() {
return emplyeeName;
}
//员工电话Set
public void SetEmplyeeTel(String emplyeeTel) {
this.emplyeeTel = emplyeeTel;
}
//员工电话Get
public String getEmplyeeTel() {
return emplyeeTel;
}
//员工住址Set
public void SetEmplyeeAddress(String emplyeeAddress) {
this.emplyeeAddress = emplyeeAddress;
}
//员工住址Get
public String getEmplyeeAddress() {
return emplyeeAddress;
}
//常规方法——获取员工信息
public String GetEmplyeeInfo(){
return emplyeeName + " " + emplyeeTel + " " + emplyeeAddress;
}
//抽象方法——获取员工住址
public abstract String GetEmplyeeAddress();
}
package my.java.study;
//普通员工——继承抽象类Employee
public class CommonStaff extends Employee {
private String workPosition;//工位
//工位Set
public void setWorkPosition(String workPosition) {
this.workPosition = workPosition;
}
//工位Get
public String getWorkPosition() {
return workPosition;
}
//抽象方法具体化
@Override
public String GetEmplyeeAddress() {
return getEmplyeeAddress();
}
}
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*抽象类*/
CommonStaff cs = new CommonStaff();
cs.setWorkPosition("001");
cs.SetEmplyeeName("徐文超");
cs.SetEmplyeeTel("13775175810");
cs.SetEmplyeeAddress("常州市新北区藻江花园");
System.out.println(cs.GetEmplyeeInfo());
System.out.println(cs.GetEmplyeeAddress());
}
}
6.封装
什么是封装
在面向对象程式设计方法中,封装(Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
封装的实现
- 修改属性的可见性来限制对属性的访问(一般限制为private)
- 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问
一般我们创建实体类的时候,通过set和get公共方法来获取字段值的方式,就是封装
7.接口
什么是接口
接口(Interface)是一个完全抽象类,定义接口使用interface关键字
接口特性
接口无法被实例化,但是可以被实现,一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
接口中的方法都是公有的。接口的继承
一个类通过继承接口的方式,从而来继承接口的抽象方法,类继承接口使用implements关键字
接口可以继承单个接口,也可以继承多个接口,接口继承接口使用extends关键字注:
JDK 1.8 以后,接口里可以有静态方法和方法体了。
JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。
JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
7.1 接口的实现和继承
定义接口的语法
[可见度] interface 接口名称 {
// 声明变量
// 抽象方法
}
接口继承接口语法
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}继承多个接口,使用逗号分割符:
[可见度] interface 接口名称 [extends 其他的接口名1,其他的接口名2]
类继承接口语法
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
[可见度] class 类名称 [implements 其他的接口名] {
// 声明变量
// 重写接口方法
}继承多个接口,使用逗号分割符:
[可见度] class 类名称 [implements 其他的接口名1,其他的接口名2]
重写接口中声明的方法时,需要注意以下规则:
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
实例代码:
package my.java.study;
//计算求和接口
public interface CalSum {
public int calIntSum(int...numbers);
public float calFloatSum(float...numbers);
//静态类方法——double求和
public static double calDoubleSum(double...numbers) {
double sum = 0;
for (double number : numbers) {
sum += number;
}
return sum;
}
}
package my.java.study;
//计算类——继承计算求和接口
public class CalHelper implements CalSum{
//计算整数求和
@Override
public int calIntSum(int... numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
//计算小数求和
@Override
public float calFloatSum(float... numbers) {
float sum = 0;
for (float number : numbers) {
sum += number;
}
return sum;
}
}
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*接口*/
//类方法求和
CalHelper calHelper = new CalHelper();
int sum1=calHelper.calIntSum(new int[]{1,2,3,4,5});
System.out.println("整数求和:"+sum1);
float sum2=calHelper.calFloatSum(new float[]{1.23f,2.4f,3.6f,4.6f,5.5f});
System.out.println("float求和:"+sum2);
//接口计算求和
double sum3=CalSum.calDoubleSum(new double[]{1.45,2.35,4.6,5.55});
System.out.println("double求和:"+sum3);
}
}
8.枚举
枚举定义
- 枚举是一个特殊的"类",它表示一组常量
- 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
- 枚举定义,应为大写字母
枚举方法
enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
values(), ordinal() 和 valueOf() 方法位于 java.lang.Enum 类中:
- values() 返回枚举类中所有的值。
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
- valueOf()方法返回指定字符串值的枚举常量。
枚举实例
public class Main {
public static void main(String[] args) {
/*枚举*/
//内部使用枚举
System.out.println(Color.RED);
//迭代遍历枚举元素
for(Color c : Color.values()){
System.out.println("枚举值:"+c);
//索引值默认 从0开始,第一个枚举默认0,后面顺延
System.out.println("枚举索引:"+c.ordinal());
}
//返回指定字符串值的枚举常量
Color cRed=Color.valueOf("RED");
System.out.println("valueOf方法:"+cRed);
cRed.colorInfo();
//switch语句中使用枚举
Color c1 = Color.BLUE;
switch (c1){
case BLUE:
System.out.println("Blue");
break;
case RED:
System.out.println("Red");
break;
case ORANGE:
System.out.println("Orange");
break;
case YELLOW:
System.out.println("Yellow");
break;
default:
System.out.println("GREEN");
break;
}
}
//定义颜色枚举
enum Color{
RED,
ORANGE,
YELLOW,
GREEN,
BLUE;
// 构造函数
private Color()
{
System.out.println("Constructor called for : " + this.toString());
}
//方法
public void colorInfo()
{
System.out.println("Universal Color");
}
}
}
9.包
包(package)的作用
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
开发者可以自己把一组类和接口等打包,并定义自己的包。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。
由于包创建了新的命名空间(namespace),所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。package 的目录结构
类放在包中会有两种主要的结果:
- 包名成为类名的一部分。
- 包名必须与相应的字节码所在的目录结构相吻合。
包语句的语法格式:
package pkg1[.pkg2[.pkg3…]];
包引入的语法格式:
import package1[.package2…].(classname|*);
包引入实例
import java.util.ArrayList; // 引入 java.util 包中的 ArrayList 类
import java.util.*; // 引入 java.util 包中的所有类
import com.example.MyClass; // 引入 com.example 包中的 MyClass 类
import com.example.*; // 引入 com.example 包中的所有类
10.反射
反射(Reflection)是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。
反射 API
反射 API 提供了一系列的类和接口来操作 Class 对象。主要的类包括:
- java.lang.Class:表示类的对象。提供了方法来获取类的字段、方法、构造函数等。
- java.lang.reflect.Field:表示类的字段(属性)。提供了访问和修改字段的能力。
- java.lang.reflect.Method:表示类的方法。提供了调用方法的能力。
- java.lang.reflect.Constructor:表示类的构造函数。提供了创建对象的能力。
工作流程
- 获取 Class 对象:首先获取目标类的 Class 对象。
- 获取成员信息:通过 Class 对象,可以获取类的字段、方法、构造函数等信息。
- 操作成员:通过反射 API 可以读取和修改字段的值、调用方法以及创建对象。
10.1 反射机制核心包
java.lang.reflect 是 反射机制的核心包,提供了操作类及其成员(字段、方法、构造函数等)的类和接口。通过这些 API,开发者可以在运行时动态地查询和修改类的结构。
java.lang.reflect的主要类和接口详细介绍
Class类:表示类的对象,提供了获取类信息的方法,如字段、方法、构造函数等。
主要方法 | 说明 |
getFields() | 获取所有公共字段 |
getDeclaredFields() | 获取所有声明的字段,包括私有字段 |
getMethods() | 获取所有公共方法 |
getDeclaredMethods() | 获取所有声明的方法,包括私有方法 |
getConstructors() | 获取所有公共构造函数 |
getDeclaredConstructors() | 获取所有声明的构造函数,包括私有构造函数 |
getSuperclass() | 获取类的父类 |
getInterfaces() | 获取类实现的所有接口 |
Field类:表示类的字段(属性),提供了访问和修改字段值的方法。
主要方法 | 说明 |
get(Object obj) | 获取指定对象的字段值 |
set(Object obj, Object value) | 设置指定对象的字段值 |
getType() | 获取字段的数据类型 |
getModifiers() | 获取字段的修饰符(如 public、private) |
Method类:表示类的方法,提供了调用方法的能力
主要方法 | 说明 |
invoke(Object obj, Object... args) | 调用指定对象的方法 |
getReturnType() | 获取方法的返回类型 |
getParameterTypes() | 获取方法的参数类型 |
getModifiers() | 获取方法的修饰符(如 public、private) |
Constructor类:表示类的构造函数,提供了创建对象的能力。
主要方法 | 说明 |
newInstance(Object... initargs) | 创建一个新实例,使用指定的构造函数参数 |
getParameterTypes() | 获取构造函数的参数类型 |
getModifiers() | 获取构造函数的修饰符(如 public、private) |
反射实例
创建Person类
package my.java.study;
public class Person {
private String name;
private int age;
//构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void greet(String message) {
System.out.println(name + " says: " + message);
}
}
反射Person类
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import my.java.study.*;
public class Main {
public static void main(String[] args) {
/*反射*/
try{
//获取class对象
//Class<?> clazz=Class.forName("my.java.study.Person");//通过名称获取
Class<?> clazz=Person.class;//通过对象获取
// 创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("John", 30);
// 获取声明的字段 name
Field nameField = clazz.getDeclaredField("name");
//设置字段为可访问(如果字段是私有的)
nameField.setAccessible(true);
System.out.println("Name: " + nameField.get(person));
// 修改name字段值
nameField.set(person, "Doe");
System.out.println("Updated Name: " + nameField.get(person));
// 调用greet方法
Method greetMethod = clazz.getMethod("greet", String.class);
greetMethod.invoke(person, "World");
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
}