1.super关键字的用法
super主要与继承有关系。一般指向(离自己最近)的父类。
几种用法:
(1).普通的直接引用
直接用super.xxx引用父类的成员(不能访问private修饰的成员)
(2).子类的成员变量(或方法)与父类的成员变量(或方法)同名时,用super进行区分
当子类和父类有同名的成员(包括属性与方法)时,可能是 方法覆盖(Override)或者 字段隐藏(Field Hiding),就可以用super来调用父类中被override的同名方法,或者父类中被隐藏的字段。
关于字段隐藏(Field Hiding)
子类在继承父类的时候,可以定义与父类中被修饰为public或protected或者default的相同名称和类型的属性,尽管它们的值可以不同。
当子类定义了一个与父类同名的属性时,子类的该属性会隐藏父类中对应的属性。这意味着,当通过子类的实例访问这个属性时,将访问到子类中定义的属性,而不是父类中的。
字段隐藏并不影响方法的多态性。也就是说,如果子类覆盖了父类的方法,那么无论是父类类型的引用还是子类类型的引用,调用该方法时都遵循多态原则,即调用的是实际对象类型的方法实现。但是,对于字段访问,Java使用的是静态绑定,即字段的访问只与引用变量的编译时类型有关,而与实际对象的类型无关。
例子:
class Parent {
String name = "Parent";
}class Child extends Parent {
String name = "Child"; // 隐藏父类的name属性
}public class Test {
public static void main(String[] args) {
Parent parent = new Parent();
Child child = new Child();
Parent parentRefToChild = new Child();System.out.println(parent.name); // 输出 Parent
System.out.println(child.name); // 输出 Child
System.out.println(parentRefToChild.name); // 输出 Parent,因为访问的是Parent类的name属性
}
}但如果,如果父类中的变量是用
private
修饰符修饰的,那么这个变量只能在父类内部被访问,对于子类来说是不可见的。在这种情况下,如果子类定义了一个同名的变量,这并不是隐藏(因为private
变量对于子类来说是不可见的),而是简单地在子类中定义了一个新的、独立的变量。
(3).引用父类的构造函数
super(参数):在子类的构造器中的第一句,可以使用super
来调用父类的构造器,这通常是为了初始化继承自父类的部分。(如果你没有在子类构造器中显式调用父类的构造器(无论是默认还是参数化的),Java编译器会插入一个默认的super()
调用到子类构造器的最开始部分。这意味着父类的无参构造器会被调用。这是属于隐式的。如果父类没有无参构造器(且没有其他构造器被显式调用),则会编译错误。)。
this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)
2.this与super的区别
相对应的,this也有三种用法:普通地引用自身、形参与成员名重名时用this区分、引用本类地构造函数
this与super的区别:
super()和this()均需放在构造方法内第一行。
尽管可以用this调用一个构造器,但却不能调用两个。
this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句 的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
this()和super()都指的是对象,所以,均不可以在static环境中【需延伸知识点】使用。包括:static变量,static方法,static语句块。
原因:
static
成员属于类本身,在类加载时执行的,而不是类的任何特定对象实例。与对象实例的创建无关。而this和super都要涉及到对象。从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
原因:
this
在Java中被用作一个引用变量,它指向了当前对象或当前正在执行的方法所属的对象。你可以将this
视为一个“指针”(虽然Java中没有指针的概念,通常我们说的是引用),它指向了对象自身。从本质上来说,
this
作为一个引用变量,直接指向了当前对象,而super
作为一个关键字,提供了一种方式来访问父类的成员(属性和方法)。this
强调的是对当前对象本身的引用,而super
强调的是对父类成员的访问权限。(this与对象的具体地址相关联,super并不储存任何父类对象的具体地址)
3.super是指针吗?
在Java中,
super
不是指针,而是一个关键字,它用于在子类中引用父类的对象。这种引用方式使得子类能够访问从父类继承的方法和属性。尽管在概念上你可以认为super
提供了一种方式来"指向"父类,但它实际上并不存储任何内存地址或指针信息,如同C或C++语言中的指针那样。
在Java中,
super
关键字能够找到对应的父类或父类的方法是通过Java的类加载机制【需延伸知识点】和运行时类型信息(RTTI)来实现的。当Java程序被编译时,每个类的信息(包括其父类信息、方法、字段等)都会被编译到生成的字节码中。这个过程确保了在运行时,Java虚拟机(JVM)能够准确地知道每个类的结构以及它们之间的继承关系。以下是
super
关键字工作原理的简化说明:
类加载时的角色:当类被加载到JVM时,它的类定义中包含了父类的引用信息。这意味着JVM在加载类时已经明确了哪个是父类。因此,当使用
super
关键字时,JVM已经具备了足够的信息来确定对应的父类。运行时解析:当代码中使用
super
关键字调用一个方法时,JVM会在运行时查找当前对象的父类,并解析出要调用的父类方法。这是通过方法调用的动态绑定实现的,其中JVM根据对象的实际类型来确定方法调用应该如何被解析和执行。编译时检查:在Java代码被编译成字节码时,编译器会检查
super
关键字的使用是否合法,比如检查是否存在对应的父类方法。这个阶段确保了在运行时使用super
时能够找到正确的方法。继承层次的遍历:如果存在多层继承,JVM会按照继承层次向上遍历,直到找到对应的方法或字段。这个过程是自动的,确保了即使在复杂的继承关系中,
super
也能正确地引用到父类的成员。总的来说,
super
关键字能够正确找到对应的父类或父类的方法,依赖于Java的编译时检查和运行时动态方法解析机制。这些机制确保了代码在运行时的行为与你通过super
所期望的父类引用是一致的。
4.项目中使用super的例子
这里有一个生成优惠券的参数DTO:
public class CouponCreateReqDTO {
/**
* 营销规则id
*/
private Long id;
/**
* 优惠券名称
*/
@NotEmpty(message = "优惠券名称不能为空!",groups = {Add.class})
@Size( max = 32, message = "优惠券名称不能超过64字节;")
private String name;
/**
* 优惠券说明
*/
@NotEmpty(message = "优惠券说明不能为空!",groups = {Add.class})
//@Size( max = 64, message = "优惠券说明不能超过128字节;")
private String memo;
/**
* 优惠券描述
*/
@NotEmpty(message = "优惠券描述不能为空!",groups = {Add.class})
@Size( max = 128, message = "优惠券描述不能超过256字节;")
private String description;
/**
* 是否模板,1是,0否
*/
@NotNull(message = "是否模板不能为空",groups = {Add.class})
private Integer temlpateType;
/**
* 开始时间,模板券不填
*/
private String beginAt;
/**
* 结束时间,模板券不填
*/
private String endAt;
/**
* 优惠券类型 1满减券 2折扣券
*/
@NotNull(message = "优惠券类型不可为空",groups = {Add.class})
private Integer type;
/**
* 有效期,年|月|日,如0|1|0表示有效期1个月
*/
private Integer templateTime;
/**
* 品牌列表,全部品牌传空列表
*/
@NotNull(message = "销售前端不能为空",groups = {Add.class})
private List<Long> brandIdList;
/**
* 类目列表,全部类目传空列表
*/
@NotNull(message = "类目列表不能为空",groups = {Add.class})
private List<Long> categoryIdList;
/**
* 商品设置,0全部商品,1指定商品
*/
@NotNull(message = "商品设置不能为空",groups = {Add.class})
private Integer productSet;
/**
* 店铺设置,0全部店铺,1全部门店,2仅官网,3指定店铺
*/
@NotNull(message = "店铺设置不能为空",groups = {Add.class})
private Integer shopSet;
/**
* 等级Id列表,全部等级传空列表
*/
@NotNull(message = "等级Id列表不能为空",groups = {Add.class})
private List<Long> levelIdList;
/**
* 状态,1启用,0禁用
*/
@NotNull(message = "状态不能为空",groups = {UpdateStatus.class,Add.class})
private Integer status;
/**
* 使用门槛,单位元
*/
@NotNull(message = "使用门槛不能为空",groups = {Add.class})
private BigDecimal threshold;
/**
* 优惠金额,单位元
*/
private BigDecimal amount;
/**
* 优惠折扣比例
*/
private BigDecimal discount;
/**
* 总数量
*/
@NotNull(message = "总数量不能为空",groups = {Add.class})
private Integer quantity;
/**
* 每人最多领取数量
*/
@NotNull(message = "每人最多领取数量不能为空",groups = {Add.class})
private Integer maxHoldQuantity;
/**
* 兑换所需积分
*/
private Integer exchangNeedPint;
/**
* 开始显示时间
*/
private String showBeginAt;
/**
* 显示结束时间
*/
private String showEndAt;
/**
* 渠道
*/
@NotNull(message = "销售前端不能为空",groups = {Add.class})
private List<Long> frontIdList;
private Integer show;
/**
* 优惠券分享图片
*/
private String sharePic;
/**
* 开始生效时间
*/
private Integer templateAvailableTime;
/**
* 是否正价券
*/
private Boolean isPositivePrice;
//忽略对应的public的get、set方法以及构造方法等
}
对应的有一个修改优惠券的参数DTO:
public class CouponUpdateReqDTO extends CouponCreateReqDTO {
@Override
@NotNull(message = "优惠券id不能为空")
@Min(value = 0, message = "优惠券id必须大于0")
@Digits(integer = 19, fraction = 0, message = "优惠券id必须为正整数")
public Long getId() {
return super.getId();
}
}
GPT对这样设计的分析:
这种设计模式是在软件开发中常见的一种做法,尤其是在处理与数据传输对象(Data Transfer Objects, DTOs)相关的场景中。
CouponCreateReqDTO
和CouponUpdateReqDTO
类的设计体现了一种继承关系,用于区分创建和更新优惠券时所需的请求数据。下面是这种设计方法的几个关键点和潜在的意义:代码复用
通过继承
CouponCreateReqDTO
,CouponUpdateReqDTO
可以复用所有在CouponCreateReqDTO
中定义的字段和方法,而不需要重新编写相同的代码。这减少了代码重复,使得代码更加简洁和易于维护。功能扩展
尽管
CouponUpdateReqDTO
在当前示例中没有新增任何字段或方法,通过重写getId
方法并添加验证注解(如@NotNull
,@Min
,@Digits
),它实际上扩展了基类的功能。这意味着在更新优惠券时,对ID的要求更为严格,确保了优惠券ID的有效性和合法性。(原来约束注释可以用在方法上!)参数验证
在
CouponCreateReqDTO
中,使用注解(如@NotEmpty
,@NotNull
,@Size
等)对各个字段进行参数验证,确保了创建优惠券请求的数据满足特定的条件。CouponUpdateReqDTO
通过继承这些设置,保证了更新操作也会进行相应的验证。此外,通过在子类中对getId
方法进行更严格的验证,进一步确保了更新操作的数据完整性和正确性。面向不同的操作定义不同的类
通过为创建和更新操作定义不同的DTO类,可以明确区分这两种操作的数据要求。虽然在很多情况下,创建和更新操作所需的数据字段可能大致相同,但也可能存在一些差异,例如在更新操作中可能需要一个明确的ID,而创建操作则不需要。通过使用继承,可以在保持代码整洁的同时,灵活地为不同的操作定义不同的数据要求。
总结
这样的设计允许开发者清晰地分离和管理不同操作(如创建和更新)所需的数据结构,同时通过继承机制来实现代码复用和功能扩展。它还利用了Java的注解和验证框架来确保数据的合法性和正确性,这对于维护数据完整性和应用安全性至关重要。