继承其实很好理解,我们天生就会继承来自父母的很多基因,那父亲拥有的很多能力你天生就会拥有甚至会发展的比父亲好,就如
博人
丸子比鸣人搓的好,向日葵
白眼比雏田强,他们的能力都是继承于【父类】,并在此基础上进行了发展,这种发展我们就称为【重写】,那么就让我们一起来看看继承在代码中的用法👇
🔥继承概念
一个类可以继承一个类,被继承的类我们称之为【父类】或者【超类】,另一个类称之为【子类】也叫【派生类】,子类可以通过extends
关键字轻松拥有获取父类的成员变量和成员方法的能力,除了被private修饰的。在java中是单继承的,这样可以规范代码的实现。继而
1> 继承的好处
<1>提高了代码的复用性,代码常见的三种复用方式:继承、组合、代理;
<2>类与类之间产生了关系,这是构成多态的前提;
2> 代码测试
接着我们通过代码尝试理解下继承:
//定义父类-波风水门,创建姓名属性和攻击的方法
public class ShuiMen {
private String name = "波风水门";
public void attack(){
System.out.println("螺旋丸");
}
}
//定义子类-漩涡鸣人,继承父类-波风水门,创建姓名属性
public class MingRen extends ShuiMen{
private String name = "漩涡鸣人";
}
//训练场测试
public class Test {
//创建鸣人的对象,看是否可以继承水门的螺旋丸
public static void main(String[] args) {
MingRen mingRen = new MingRen();
mingRen.attack();
}
}
让我们在训练场进行测试:
🌌方法重写
波风水门创造出了螺旋丸
,我们知道漩涡鸣人在螺旋丸
的基础上就将螺旋丸改进出了近20种不同的螺旋丸,如大玉螺旋丸
、太极螺旋丸
、惑星螺旋丸
、仙法·磁遁·螺旋丸
……,那结合上述的代码,我们是不是可以让子类MingRen改进父类ShuiMen的attack方法,答案是可以的,而这种改进就是方法的重写。
1> 方法重写概念
子类中出现与父类一模一样的方法时(返回值类型、方法名、参数列表相同),子类中的方法会覆盖父类中的方法,这种覆盖就是重写。
重写的方法上面加上注解:@Override
,表示该方法进行了重写。
2> 重写的应用
我们在工作中可以根据自己的需求,对我们所继承的父类中的一些方法进行重写,在子类中定义一些特定的属性行为,从而对弗雷德方法进行扩展增强。
注意:重写父类的方法时,子类不能缩小父类方法的范围和权限。
3> 代码测试
接替上述代码,我们实现下覆盖既重写,在MingRen类中改动:
//定义子类-漩涡鸣人,继承父类-波风水门,创建姓名属性,重写父类攻击方法
public class MingRen extends ShuiMen{
private String name = "漩涡鸣人";
@Override
public void attack() {
System.out.println("哈~超大玉螺旋丸");
}
}
让我们在训练场进行测试:
💖super关键字
是不是浮现出了this
关键字呢(☞゚ヮ゚)☞,我们知道this
指向的是调用该方法的实例对象;同样的super
关键字指向的是父类的实例对象;
1> 代码实例
现在我们先给父类和子类添加无参构造方法,然后我们在训练场实例化子类;
//定义父类-波风水门,创建姓名属性,无参构造
public class ShuiMen {
private String name = "波风水门";
public ShuiMen() {
System.out.println("我是波风水门");
}
}
//定义子类-漩涡鸣人,继承父类-波风水门,创建姓名属性,无参构造
public class MingRen extends ShuiMen{
private String name = "漩涡鸣人";
public MingRen() {
System.out.println("我是旋涡鸣人");
}
}
//训练场测试
public class Test {
//创建鸣人的对象,看是否可以执行水门的构造方法
public static void main(String[] args) {
MingRen mingRen = new MingRen();
}
}
训练场测试结果如下:
2> 继承中构造方法的相关问题
结论:通过上述执行结果我们可以看出,构造一个子类一定会先构造一个父类,如果构造孙子,也是先会构造祖父类的,不妨我们可以加入BoRen类,然后再训练场构造BoRen实例:
//定义子类-漩涡博人,继承父类-旋涡鸣人,创建姓名属性,重写父类攻击方法
public class BoRen extends MingRen{
private String name = "漩涡博人";
public BoRen() {
System.out.println("我是漩涡博人");
}
}
//训练场测试
public class Test {
//创建博人的对象,看是否可以执行水门的构造方法
public static void main(String[] args) {
BoRen boren = new BoRen();
}
}
训练场测试结果如下:
因此我们可以总结出两点:
1> 使用
super
调用父类的非私有方法和属性时,大致过程如下:
- 先在当前类中寻找。
- 当前类没有,继续向父类中寻找。
- 如果还是没有,就向父类的父类继续寻找。
- 直到到达一个所有类的共同父类,他叫
Object
。
那么问题来了,我想使用父类的属性,直接用就行了,super
有啥用啊。是啊,那如果子类也定义了相同名字的属性呢?因此我们还是需要super
里访问父类的属性。
2> 在子类的构造方法中,访问父类的构造方法
我们结合上述的代码,再深入探索一下子类调用的是父类的无参构造
还是有参构造
,我们在ShuiMen类中增加无参构造和有参构造及get,set方法,在MingRen类中也添加,然后我们进行测试:
//定义父类-波风水门
public class ShuiMen {
private String name = "波风水门";
public ShuiMen() {
System.out.println("我是波风水门");
}
public ShuiMen(String name) {
this.name = "四代目";
}
public String getName() {
return name;
}
}
//定义子类-漩涡鸣人,继承父类-波风水门
public class MingRen extends ShuiMen{
private String name = "漩涡鸣人";
public MingRen() {
System.out.println("我是旋涡鸣人");
}
public MingRen(String name) {
this.name = name;
}
}
//训练场测试
public class Test {
//创建鸣人的对象,打印名字属性,看是否执行了有参构造
public static void main(String[] args) {
MingRen mingRen = new MingRen();
System.out.println(mingRen.getName());
}
}
执行结果如下:
由执行结果我们可以看出,创建MingRen类实例时默认执行了两个无参构造
,并且在ShuiMen类中有参构造没有被执行,切打印出来的名字依旧是波风水门
,因此子类在构造的时候只会默认调用父类的【空参构造】
。那如果子类要通过父类的有参构造,又该怎么办呢?我们对上述代码进行改进:
//定义父类-波风水门
public class ShuiMen {
private String name = "波风水门";
public ShuiMen() {
System.out.println("我是波风水门");
}
public ShuiMen(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//定义子类-漩涡鸣人,继承父类-波风水门
public class MingRen extends ShuiMen{
private String name = "漩涡鸣人";
public MingRen() {
super("四代目");
System.out.println("我是旋涡鸣人");
}
public MingRen(String name) {
super(name);
}
}
//训练场测试
public class Test {
//创建鸣人的对象,打印名字属性,看是否执行了有参构造
public static void main(String[] args) {
MingRen mingRen = new MingRen();
System.out.println(mingRen.getName());
}
}
执行结果如下:
由此我们可以看出此程序先执行了子类的空参构造
,然后通过Super()
调用了父类的有参构造,从而实现了子类访问父类有参构造的需求;在此程序中,我们也可以总结出两个知识点:
1>
super()
和this()
只能放在方法中的第一行
,并且在一个方法中不能同时出现
。
在改进上述代码时,我们发现了如下图所示错误:
其中指明Call to 'super()' must be first statement in constructor body,因此【super构造器只能放在第一行】,如图所示:
其实很好理解,上述讲到的那个结论提到【构造一个子类一定会先构造一个父类,如果构造孙子,也是先会构造祖父类的】,所以父类还没有构造,你的代码凭什么执行?
2>
super()
和this()
不会向上检索
this | super | |
---|---|---|
访问属性 | 访问本实例的属性,没有会继续向父类检索 | 访问父类实例的属性,没有会继续向父类检索 |
调用方法 | 访问本实例的方法,没有会继续向父类检索 | 访问父类实例的方法,没有会继续向父类检索 |
调用构造器 | 调用本类的构造器,必须放在第一行,不会向上检索 | 调用父类的构造器,必须放在第一行,不会向上检索 |
3> 总结
对super关键字总结以下几点:
- 子类继承了父类所有的非私有的属性和方法,可以直接调用。
- 子类在构造的时候,一定会构造一个父类,默认调用父类的无参构造器。
- 子类如果希望指定去调用父类的某个构造器, 则显式的调用一下 : super(参数列表)
- super和this当做构造器使用时, 必须放在构造器第一行,所以只能二选一。
- java 所有类都是 Object 类的子类, Object 是所有类的基类.
- 子类最多只能继承一个父类(指直接继承), java 中是单继承机制,我们可以使用连续继承来实现。
🍜final关键字
final
作为一个关键字,他可以修饰变量,方法,以及类,final就是最终
的意思:
1、被final
修饰的变量不能被修改
,这里有两层含义,如果final修饰的是基础数据类型是只不能被修改,如果是引用数据类型就是引用指向不能被修改。
2、被final修饰的方法不能被重写
。
3、被final修饰的类不能被继承
。
🐱👤祖先类Object
Object类有11个方法,其中有八个是公共方法,在此我们先学习几个基本的方法:
(1) hashCode
方法的定义:
public native int hashcode();
所有带有native
的方法都是本地方法,他不是java写的。这个hashcode的返回值其实是实例对象运行时的内存地址。hash算法是其发展的重点,那什么是hash算法呢?
一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。
hash算法的几个特点:
- 只能通过原文计算出hash值,而且每次计算都一样,不能通过hash值计算原文;
- 原文的微小变化就能使hash值发生巨大变化;
- 一个好的hash算法还能尽量避免发生hash值重复的情况,也叫hash碰撞。
hash算法的用途:
- 密码的保存;
- 文件的校验,检查数据的一致。
常见的hash算法:
MD5
import java.security.MessageDigest;
import java.util.Arrays;
public class Hash {
public static void main(String[] args) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] digest = md5.digest("123".getBytes());
System.out.println(Arrays.toString(digest));
}
}
SHA1
public class Hash {
public static void main(String[] args) throws Exception {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
byte[] digest = sha1.digest("123".getBytes());
System.out.println(Arrays.toString(digest));;
}
}
SHA256
:SHA256算法使用的哈希值长度是256位。
public class Hash {
public static void main(String[] args) throws Exception {
MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
byte[] digest = sha1.digest("123".getBytes());
System.out.println(Arrays.toString(digest));;
}
}
SHA512
:SHA512算法使用的哈希值长度是512位。
public class Hash {
public static void main(String[] args) throws Exception {
MessageDigest sha1 = MessageDigest.getInstance("SHA-512");
byte[] digest = sha1.digest("123".getBytes());
System.out.println(Arrays.toString(digest));;
}
}
(2) equals
Tips
:较所有的引用数据类型使,都要使用equals
。
Object中equals方法的源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
【总结】
- ==可以比基础数据类型也可以比较引用数据类型,比较
基础数据类型
时比较的是具体的值
,比较引用数据类型
实际上比较的是内存地址
。 - equals是Object的一个方法,默认的实现就是 ==。
- 我们
可以重写equals方法
,满足我们的特性需求,比如String就重写了equals方法,所以字符串调用equals比较的是每一个字符。
比如,编写一个类Student,我们需要比较两个学生的信息,如果编号和姓名一样,我们就认为是同一个人,因此我们就可以重写equals方法,具体如下:
public class Student {
//创建编号、名字、性别属性
private int id;
private String name;
private String sex;
//创建构造方法
public Student(int id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
//创建get\set方法
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
//重写equals()
@Override
public boolean equals(Object o){
if (this == o) return true;
if (o == null) return false;
Student student = (Student) o;
return id == student.id && student.getName().equals(name);
}
//主函数进行测试
public static void main(String[] args) {
Student student1 = new Student(1,"张三","男");
Student student2 = new Student(1,"张三","女");
System.out.println(student1.equals(student2));
}
}
执行结果如下:
(3) toString
此方法就是将一个实例化对象转化成一个可以打印的字符串。默认的打印的方法就是默认调用Student的toString方法。
在上面代码的基础上我们对toString()进行测试训练:
//重写toString()
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + ''' +
", sex='" + sex + ''' +
'}';
}
//主函数进行测试
public static void main(String[] args) {
Student student1 = new Student(1,"张三","男");
Student student2 = new Student(1,"张三","女");
//此处可以不用student1.toString(),因为打印方法会默认调用刚才重写的toString()
System.out.println(student1);
}
结果如下:
(4) clone
克隆就是在内存里边赋值一个实例对象。但是Object的克隆方法只能浅拷贝。同时必须实现Cloneable接口。
深拷贝与浅拷贝的区别: 浅拷贝就如鸣人的影分身,都使用的是鸣人自己的查克拉;而深拷贝就像卡卡西的复制,鸣人用自己的查克拉进行影分身,卡卡西copy后也可以用自己的查克拉进行影分身。
在Java中深拷贝和浅拷贝指的都是对象的拷贝:
浅拷贝: 被复制的对象的所有的变量都与原对象有相同的值,而所有的引用对象仍然指向原来的对象。
深拷贝: 除了被复制对象的所有变量都有原来对象的值之外,还把引用对象也指向了被复制的新对象。