前言
前面我们已经学了继承,在继承中我们会发现,子类重写了父类的方法,最终使用的子类的方法,而父类中方法的方法体没啥用,那么是否能把它剩略呢?另一个问题就是类不能多继承,子类的功能是不方便的扩展与维护的。这两个问题都会随着本章所讲解的抽象类与接口所回答,接下来就让我们带着问题进入本章的阅读。
文章目录
🌍1.1抽象类
🌍什么是抽象类?
在java中类是用来描述对象的信息,当一个类没有包含足够的信息去描述一个具体的对象,那么这样的类被称为抽象类。比如
圆形,正方形,三角形都属于图形,因此和图形的关系是继承关系。而图形类中虽然有draw()方法,但是图形类不是一个具体的形状,所以无法去画一个具体的图形。又因为图形类本身就是通过对圆形,正方形,三角形等图形进一步抽象而来的类,导致其方法是无法具体实现,所以可以将该类称之为抽象类。
🌍1.2为什么要使用抽象类?
先来看一段代码
public class Shape { public void draw(){ System.out.println("画一个图形"); } } public class Circular extends Shape{ public void draw(){ System.out.println("画一个圆形"); } } public class Square extends Shape{ public void draw(){ System.out.println("画一个正方形"); } } public class Triangle extends Shape{ public void draw(){ System.out.println("画一个三角形"); } } public class Text { public static void main(String[] args) { Shape c = new Circular(); c.draw(); Shape t = new Triangle(); t.draw(); Shape s = new Square(); s.draw(); } }
圆形,正方形,三角形都重写了父类Shape中的draw()方法,最终执行的是子类中的方法,而父类中draw();方法体中的内容一点用都没有。此时就可以将该方法定义为抽象方法,而包含抽象方法得到类则定义为抽象类。
🌍1.3抽象类的语法
在java中,如果一个类被abstract修饰则称之为抽象类,抽象类中的倍abstract修饰的方法被称为抽象方法,而抽象方法是可以不写具体的方法体。
代码示例
public abstract class Shape { public int area; private double height; public static final int width =90; public Shape(int area, double height) { this.area = area; this.height = height; } public abstract void draw(); public void print(){ System.out.println("我是一个实例方法"); } public static void method(){ System.out.println("我是一个静态方法"); } public int getArea() { return area; } public void setArea(int area) { this.area = area; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public static int getWidth() { return width; } }
抽象类与普通类最大的区别就是抽象类被abstract,以及该类中可以定义抽象方法。在其它方面基本与普通类差不多,如抽象类可以定义实例成员变量,可以提供构造器,可以定义实例方法与静态方法。
🌍1.4抽象类特性
1.抽象类不能直接实例化对象
Shape s=new Shape();
创建对象是通过一个具体的类,描述出一个具体的对象。而图形本身就是抽象出来的概念,将它实例出来的图形还是图形,那么图形是啥呢?而实例化对象是获得一个具体对象,所以抽象类不能直接实例化对象。
2.抽象方法不能是 private 修饰的
public abstract class Shape { private abstract void draw(); }
private修饰的成员只能在本类中访问,而抽象方法被其它子类访问并重写的。
注意:抽象方法没有加访问限定符时,默认是public.
3.抽象方法不能被final和static修饰,因为抽象方法要被子类重写
public abstract class Shape { public final abstract void draw(); } public abstract class Shape { public static abstract void draw(); }
final修饰方法都不能被重写,static修饰的方法也不能被继承。
4.抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
代码示例
public abstract class Shape { public abstract void draw(); public abstract void rotating(); } public abstract class Triangle extends Shape{ public abstract void print(); } public class LsoscelesTriangle extends Triangle { @Override public void draw() { System.out.println("画一个等腰三角形"); } @Override public void print() { System.out.println("我是一个等腰三角形"); } @Override public void rotating() { System.out.println("等腰三角形旋转90度"); } } public class Text { public static void main(String[] args) { LsoscelesTriangle l=new LsoscelesTriangle(); l.draw(); l.print(); } }
三角形定义为抽象类并继承图形类是可以不重写图形类中的draw();方法,而等腰三角形类继承了三角形类,所以它要重写图形类和三角形类中的所有的抽象方法。
5.抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
6.抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
1.5🌍抽象类的使用总结与注意事项
- 1.抽象类可以理解成类的不完整设计图,是用来被子类继承的。
- 2.一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
- 3.不能用abstract修饰变量、代码块、构造器。
- 4.有得有失: 得到了抽象方法,失去了创建对象的能力。
🌍1.6final和abstract是什么关系?
互斥关系
- 1.abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 2.抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
🌍2.1接口
🌍2.2什么是接口?
接口在我们现实中随处可见,比如笔记本电脑上的USB接口,它可以插U盘,鼠标,键盘,所有符合USB协议的设备,比如电源插座上的插孔,可以插符合插孔规范的电脑,电视机等设备,比如寝室们的锁孔,凡是符合这把锁的钥匙都能插,并且打开门。通过这些例子可以得出一个结论:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。 在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
🌍2.3为什么要使用接口?
先来看一段代码
public abstract class Animal { abstract void eat(); abstract void run(); abstract void Swimming(); abstract void fly(); } public class Bird extends Animal{ @Override void eat() { System.out.println("鸟吃虫子"); } @Override void run() { System.out.println("鸟用小爪子跳着走"); } @Override void Swimming() { } @Override void fly() { System.out.println("鸟用翅膀飞"); } } public class Dog extends Animal { @Override void eat() { System.out.println("狗吃肉"); } @Override void run() { System.out.println("狗用4条腿跑"); } @Override void Swimming() { } @Override void fly() { } } public class Fish extends Animal{ @Override void eat() { System.out.println("鱼吃微生物"); } @Override void run() { } @Override void Swimming() { System.out.println("鱼在水里游泳"); } @Override void fly() { } }
定义一个抽象的动物类,并提供抽象方法,然后让狗,鱼,鸟三个动物继承并重写该类中的方法后,就会产生一个问题,那就是狗是不会游泳的,是不会飞的,同理其它物种也是一样,这与实际不符合,此时就需要接口来解决和这个问题,将某些行为定义成接口,然后让子类实现接口即可。
接口语法规则
🌍接口的定义与特点
接口用关键字interface来定义
public interface 接口名 { // 常量 // 抽象方法 }
代码示例
public interface Fly { //public abstract void fly(); //public static final int a=90; void fly(); int a=90; }
在接口中默认的方法是抽象方法,变量默认是常量。所以修饰符可以直接省略。
注意
- 1.创建接口时, 接口的命名一般以大写字母 I 开头.
- 2.阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
- 3.接口的命名一般使用 “形容词” 词性的单词
🌍2.4接口使用
接口自身就是过度抽象的类不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
代码示例
public interface IUSB { void openDevice(); void closeDevice(); } public class KeyBoard implements IUSB{ @Override public void openDevice() { System.out.println("打开键盘"); } @Override public void closeDevice() { System.out.println("关闭键盘"); } public void inPut(){ System.out.println("键盘输入"); } } public class Mouse implements IUSB{ @Override public void openDevice() { System.out.println("使用鼠标"); } @Override public void closeDevice() { System.out.println("不使用鼠标"); } public void click(){ System.out.println("鼠标点击"); } } public class Computer { public void powerOn(){ System.out.println("打开笔记本电脑"); } public void powerOff(){ System.out.println("关闭笔记本电脑"); } public void useDevice(IUSB usb){ usb.openDevice(); if(usb instanceof Mouse){ Mouse mouse = (Mouse)usb; mouse.click(); }else if(usb instanceof KeyBoard){ KeyBoard keyBoard = (KeyBoard)usb; keyBoard.inPut(); } usb.closeDevice(); } } public class Text { public static void main(String[] args) { Computer computer = new Computer(); computer.powerOn(); // 使用鼠标设备 computer.useDevice(new Mouse()); // 使用键盘设备 computer.useDevice(new KeyBoard()); computer.powerOff(); } } 运行结果 //使用鼠标 //鼠标点击 //不使用鼠标 //打开键盘 //键盘输入 //关闭键盘 //关闭笔记本电脑
🌍2.5接口特性
1.接口类型是一种引用类型,但是不能直接new接口的对象
接口是抽象类进一步的抽象,凡是抽象的都不能实例化对象
public class Text2 { public static void main(String[] args) { IUSB i=new IUSB(); } }
2.接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
public interface IUSB { private abstract void openDevice(); private abstract void closeDevice(); }
3.接口中的方法是不能在接口中实现的,只能由实现接口的类来实现。
public interface USB { void openDevice(); // 编译失败:因为接口中的方式默认为抽象方法 // Error:(5, 23) java: 接口抽象方法不能带有主体 void closeDevice(){ System.out.println("关闭USB设备"); } }
接口中的方法是抽象方法是被实现类所所重写的,不能在接口中实现。
4.接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
public interface IUSB { //public static final int a=90; int a=90; }
5.接口中不能有静态代码块和构造方法
public interface USB { // 编译失败 public USB(){ } { } // 编译失败 void openDevice(); void closeDevice(); }
因为接口是进一步的抽象,在接口里面所有定义的变量都默认是常量,而常量必须就地初始化,因此没有必要提供构造器或者代码块来初始化成员变量。
6.使用default关键字定义的方法是接口默认的方法
public interface IUSB { default void print(){ System.out.println("我是USB接口"); } }
7.重写接口中方法时,不能使用default访问权限修饰
public interface IUSB { void openDevice(); void closeDevice(); } public class KeyBoard implements IUSB{ @Override default void openDevice() { System.out.println("打开键盘");//编译报错 } @Override default void closeDevice() { System.out.println("关闭键盘");//编译报错 } public void inPut(){ System.out.println("键盘输入"); } }
实现类中重写的方法的访问权限需大于等于接口中,而接口中方法的访问权限修饰符默认是public,所以实现类中所重写的方法必须用public修饰。
🌍2.6注意事项
- 1.接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
- 2.如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
- 3.dk8中:接口中还可以包含default方法。
实现多个接口
java中不能多继承只能单继承也就是不能同时拥有多了亲爹,但是java支持类有多个干爹,这个干爹就是接口。
代码示例
定义一个动物类
public class Animal { protected String name; public Animal(String name) { this.name = name; } }
实现会飞,会游泳,会跑等是三个接口
public interface IFly { void fly(); } public interface IRunning { void run(); } public interface ISwimming { void swim(); }
猫只能凭借4条腿跑
public class Cat extends Animal implements IRunning{ public Cat(String name) { super(name); } @Override public void run() { System.out.println(name+"四条腿跑的快"); } }
鸟是会飞的
public class Bird extends Animal implements IFly{ public Bird(String name) { super(name); } @Override public void fly() { System.out.println(name+"用翅膀飞"); } }
青蛙既能游泳又能跑
public class Frog extends Animal implements IRunning,ISwimming { public Frog(String name) { super(name); } @Override public void run() { System.out.println(name+"跳着走"); } @Override public void swim() { System.out.println(name+"游泳"); } }
鸭子既能跑,又能游泳,更能飞。
public class Duck extends Animal implements IFly,IRunning,ISwimming{ public Duck(String name) { super(name); } @Override public void fly() { System.out.println(name+"用鸭翅膀飞"); } @Override public void run() { System.out.println(name+"用大脚趾跑"); } @Override public void swim() { System.out.println(name+"游泳"); } }
上述代码都是一个类继承一个类,同时实现多个接口。继承体现类与类之间的关系是is -a关系,接口是体现了事物xxx的特征。
比如猫是一只动物,具有跑的特征,青蛙是一只动物,既能跑,也能游泳。
有了接口后,类的实现者不必关心具体的类型,而只要关注类是否具备某种能力。
比如,现在实现一个吃东西的方法
public class Text { public static void main(String[] args) { Bird b = new Bird("小鸟"); Cat c = new Cat("小猫"); Frog f = new Frog("青蛙"); Duck d = new Duck("大黄鸭"); run(b); run(c); run(f); run(d); } public static void run(IRunning run) { run.run(); } } //运行结果 小鸟会用爪子跑 小猫四条腿跑的快 青蛙跳着走 大黄鸭用大脚趾跑
上述代码等价于
public class Text { public static void main(String[] args) { IRunning b = new Bird("小鸟"); IRunning c = new Cat("小猫"); IRunning f = new Frog("青蛙"); IRunning d = new Duck("大黄鸭"); b.run(); c.run(); f.run(); d.run(); } } //运行结果 小鸟会用爪子跑 小猫四条腿跑的快 青蛙跳着走 大黄鸭用大脚趾跑
实现接口可以看做特殊的继承,上述的代码就是多态的体现,父类引用指向子类对象,最终调用的是具体子类的方法。
接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到 多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
public interface IRunning { void run(); } public interface ISwimming { void swim(); } public interface IAmphibious extends IRunning,ISwimming{ } public class Frog2 implements IAmphibious { @Override public void run() { System.out.println("青蛙跳着走"); } @Override public void swim() { System.out.println("青蛙游泳"); } }
通过接口继承创建了一个新的接口IAmphibious,而frog2实现了这个接口,就要重写该接口里面的swim()方法以及run()方法。
🌍2.7接口使用实例
🌍2.8给引用类型的数据排序
####🌍2.9 接口Comparable的使用
首先准备2个学生类。
public class Student { private String name; private int age; private double score; public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } 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 double getScore() { return score; } public void setScore(double score) { this.score = score; } @Override public String toString() { return "Studnet{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } }
学生测试类
public class StudentText { public static void main(String[] args) { Student s=new Student("叶秋涵",19,100); Student s2=new Student("叶子秋",15,99); } }
既然要对引用类型的数据进行排序,那么是否是运算符来比较大小呢?肯定是不可以的,对象与对象的比较,如同人与人之间的比较,总要具体到某一件事上才能比较,好比家人总是说你张三成绩比你好,这就是比较了2对象之间的成绩。所以不是拿对象那本身来互相比较,而是具体到对象的某一属性来比较。
为了指定到对象的某一属性来进行比价,我们让Student实现Comparable接口,并实现其中的compareTo接口。
public class Student implements Comparable<Student>{ private String name; private int age; private double score; public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } 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 double getScore() { return score; } public void setScore(double score) { this.score = score; } @Override public String toString() { return "Studnet{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } @Override public int compareTo(Student o) {//按年龄比较 return this.age-o.age; } }
Studnet测试类
public class StudentText { public static void main(String[] args) { Student s=new Student("叶秋涵",11,100); Student s2=new Student("叶子秋",15,99); if(s.compareTo(s2)>0){ System.out.println("s>s2"); }else if(s.compareTo(s2)<0){ System.out.println("s<s2"); }else{ System.out.println("s==s2"); } } }
接口compareTo()的比较规则
1.如果认为左边数据大于右边数据则返回正整数
2.如果认为左边数据小于右边数据则返回负整数
3.如果认为左边数据等于右边数据则返回负整数
4.默认是升序,若要降序将比价的2个对象的参数互换即可。即o2-o1.
5.使用Arrays.sort();对引用类型的数组进行排序。
示例代码一
public class Student /*implements Comparable<Student>*/{ private String name; private int age; private double score; public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } 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 double getScore() { return score; } public void setScore(double score) { this.score = score; } @Override public String toString() { return "Studnet{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } // @Override // public int compareTo(Student o) { // return this.age-o.age; // } } public class StudentText { public static void main(String[] args) { Student [] arr=new Student[3]; arr[0]=new Student("叶秋涵",11,100); arr[1]=new Student("叶子秋",15,99); arr[2]=new Student("老衲爱尼姑",13,39); // if(arr[0].compareTo(arr[1])>0){ // System.out.println("s>s2"); // }else if(arr[0].compareTo(arr[1])<0){ // System.out.println("s<s2"); // }else{ // System.out.println("s==s2"); // } Arrays.sort(arr); System.out.println(Arrays.toString(arr)); } }
代码无法编译过去,因为sort();会将传入的对象强制转换为Comparable,而此时的Student没有实现Comparable是无法强转的,同时sort()方法会自动调用compareTo()方法,也就是意味着对引用类型的数组进行排序也要事先指定排序规则。
Student实现了Comparable并重写了ompareTo(),最后成功对该数组排序。
[Studnet{name='叶秋涵', age=11, score=100.0}, Studnet{name='老衲爱尼姑', age=13, score=39.0}, Studnet{name='叶子秋', age=15, score=99.0}]
🌍2.9 自定义比较器( Comparator)
如果我们需要按分数对学生排序,那么按照上述的代码,我们还需修改compareTo()中的排序规则,某一天又需要对年龄比较又要修改回来,这样就非常麻烦此时需要另一个接口Comparator(),来解决此问题。
自定义年龄比较器
ublic class AgeComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o1.getAge()-o2.getAge(); } }
自定义分数比较器
class ScoreComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return Double.compare(o1.getScore(), o2.getScore()); } }
自定义姓名比较器
public class NameComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); } }
补充字符串的排序比较
使用字符串的compareTo();接口来比较。
int compareTo(String anotherString) ,虽然返回值是Int,但是实际比较的两个字符串的ascii码值。返回的值(正数、负数、0)有三种情况:
1.如果第一个字符和参数的第一个字符不等,结束比较,返回与第一个字符的ASCII码差值;
2.如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,直到不同字符返回差值;
3.如果两个字符串为子串关系,则返回两个字符串的长度差值。
使用比较器对学生数组进行排序
public class Text { public static void main(String[] args) { Student [] arr=new Student[3]; arr[0]=new Student("叶秋涵",11,100); arr[1]=new Student("子秋",15,99); arr[2]=new Student("老衲爱尼姑",13,39); System.out.println("按年龄排序"); AgeComparator ageComparator=new AgeComparator(); Arrays.sort(arr,ageComparator); System.out.println(Arrays.toString(arr)); System.out.println("按分数排序"); ScoreComparator scores=new ScoreComparator(); Arrays.sort(arr,scores); System.out.println(Arrays.toString(arr)); System.out.println("按字典序排序"); NameComparator name=new NameComparator(); Arrays.sort(arr,name); System.out.println(Arrays.toString(arr)); } } 运行结果 按年龄排序 [Studnet{name='叶秋涵', age=11, score=100.0}, Studnet{name='老衲爱尼姑', age=13, score=39.0}, Studnet{name='子秋', age=15, score=99.0}] 按分数排序 [Studnet{name='老衲爱尼姑', age=13, score=39.0}, Studnet{name='子秋', age=15, score=99.0}, Studnet{name='叶秋涵', age=11, score=100.0}] 按字典序排序 [Studnet{name='叶秋涵', age=11, score=100.0}, Studnet{name='子秋', age=15, score=99.0}, Studnet{name='老衲爱尼姑', age=13, score=39.0}]
🌍3.0Clonable 接口和深拷贝
java 中内置了一些很有用的接口, Clonable 就是其中之一. Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要 先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。要拷贝的类实现了Clonable();该接口是个空接口,也就是接口里面没有任何的抽象方法,实现该接口只是为了标记要拷贝的对象,以便能成功拷贝。
🌍3.1Clonable的使用
public class Person implements Cloneable{ public int age; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Person{" + "age=" + age + '}'; } public static void main(String[] args) throws Exception { Person p=new Person(); p.age=90; System.out.println(p); Person p2=(Person) p.clone(); System.out.println(p2.age); System.out.println("============"); p2.age=99; System.out.println(p2.age); } } 运行结果 Person{age=90} 90 ============ 99
上述代码内存解析
clone方法克隆了一份对象p并返回一个Object的对象,由于该方法的返回值为Object,所以需将其强转为Person在赋值给Person。当我们通过p2引用修改age可以知道不会修改引用p所指向对象中的age,因为这是两份内存,是互不干扰的。
🌍3.2浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向 原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.
浅拷贝代码示例
public class Person implements Cloneable{ public int age; Wallet w=new Wallet(); @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Person{" + "age=" + age + '}'; } } public class Wallet { double money = 99; } public class WalletText { public static void main(String[] args) throws CloneNotSupportedException { Person p = new Person(); Person p1 = (Person) p.clone(); System.out.println(p1.w.money); System.out.println("修改后"); p1.w.money=12; System.out.println(p1.w.money); } } 运行结果 //99.0 //修改后 //12.0
浅拷贝内存图
🌍3.3深拷贝
被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被 复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都 复制了一遍
public class Wallet implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } double money = 99; @Override public String toString() { return "Wallet{" + "money=" + money + '}'; } } public class Person implements Cloneable { public int age; public Wallet w = new Wallet(); @Override protected Object clone() throws CloneNotSupportedException { Person tmp = (Person) super.clone(); tmp.w = (Wallet) this.w.clone(); return tmp; } @Override public String toString() { return "Person{" + "age=" + age + ", w=" + w + '}'; } } public class WalletText2 { public static void main(String[] args) throws CloneNotSupportedException { Person p=new Person(); p.age=90; System.out.println(p); System.out.println("修改后"); Person p1=(Person) p.clone(); p1.age=90; p1.w.money=900; System.out.println(p1); } } 运行结果 Person{age=90, w=Wallet{money=99.0}} 修改后 Person{age=90, w=Wallet{money=900.0}}
深拷贝内存图
为了完成深拷贝,只需将引用p所指的对象先拷贝,后将p对象的w引用所指向的对象再拷贝一份,最后修改p1中引用w的地址即可。为了完成这个过程需要拷贝Wallet,故该类也需实现Cloneable并重写clone()方法,还需一个中间变量tmp去修改p1中引用w的地址。
🌍3.4抽象类和接口的区别
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中 不能包含普通方法, 子类必须重写所有的抽象方法
普通区别
1.从组成来说,抽象类是由普通类加抽象方法组成,而接口是抽象方法+全局常量。
2.从访问权限来说,抽象类可以使用各种权限修饰符,而接口只能用public
3.从子类角度来说,子类使用extends继承抽象类,对于实现接口则是用implements
4.从类与类之间的关系来说,一个抽象类可以实现多个接口,但是接口不能继承抽象类,但能使用extends关键字继承多个接口。
5.从子类角度来说,一个子类只能继承一个抽象类,但是能实现多个接口。
🌍4.1Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父 类。即所有类的对象都可以使用Object的引用进行接收。
代码示例使用Object接收其它类的引用
public class Text { public static void main(String[] args) { fun(new Person()) fun(new Wallet()); } private static void fun(Object o) { System.out.println(o); } } 运行结果 clonable.Person@1b6d3586 clonable.Wallet@4554617c
🌍4.1获取对象信息
如果要打印对象中的内容,可以直接重写Object类中的toString()方法,不重写打印的地址。
// Object类中的toString()方法实现: public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
只需用idea重写toString()即可,打印对象的信息。
🌍4.2equals()
在Java中,= =进行比较时:
- 1.如果== 左右两侧是基本类型变量,比较的是变量中值是否相同
- 2..如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
- .3.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
代码示例
没有重写equals
public static void main(String[] args) { Person p=new Person(); p.age=90; Person p1=new Person(); p1.age=90; System.out.println(p.age == p1.age); System.out.println(p == p1); System.out.println(p.equals(p1)); } 运行结果 true false false
重写了equals
public static void main(String[] args) { Person p=new Person(); p.name="叶秋涵"; Person p1=new Person(); p1.name="叶秋涵"; System.out.println(p == p1); System.out.println(p.name.equals(p1.name));//比较对象的属性 运行结果 false tru
🌍4.3hashcode方法
回忆刚刚的toString方法的源码:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
调用hashCode();返回哈希值,然后通过Integer.to没有重写hashCode()l;HexString以16进制打印出来。
hashcode方法源码:
public native int hashCode();
该方法是一个native方法,底层是由C/C++代码写的。我们看不到。 我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,我们可以来看示例 代码:
没有重写hashCode()l;
public class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; } } public class Text { public static void main(String[] args) { Person p=new Person("阿茂",19); Person p2=new Person("阿茂",19); System.out.println(p.hashCode()); System.out.println(p2.hashCode()); } } 运行结果 460141958 1163157884
注意事项:两个对象的hash值不一样。
重写hashCode();后
public class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public int hashCode() { return Objects.hash(name, age); } } public class Text { public static void main(String[] args) { Person p=new Person("阿茂",19); Person p2=new Person("阿茂",19); System.out.println(p.hashCode()); System.out.println(p2.hashCode()); } } 运行结果 38003601 38003601
注意事项:哈希值一样。
结论:1、hashcode方法用来确定对象在内存中存储的位置是否相同
2、事实上hashCode() 在哈希表中才有用,在其它情况下没用。在哈希表中hashCode() 的作用是获取对象的哈希值,进而确定该对象在哈希表中的位置。
最后的话
各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!