目录
前言
通过前面的知识,已经打下了基础,下面还是围绕着类这个话题展开学习。
各位看官请指教!
一、抽象类详解
1.1 什么是抽象类
Java是面向对象的语言,他的存在就是对世界中的实体进行描述;而所有的对象都是通过类实例化,我们可以对对象进行细致化的描述,但是呢,并不是所有的对象都用来描述对象,如果一个类中没有能力去具体的描述某一个对象,这样的类就是抽象类。下面我们用一段代码来解答:
//整个抽象类的代码都整合在一起
/*class GoSchool{
public void goSchool(){
System.out.println("怎样的方式去上学!");
}
}*/
abstract class GoSchool{
//抽象类的方法直接用分号结尾
public abstract void goSchool();
//public static abstract void goSchool();//err
//public final abstract void goSchool();//err
//private abstract void goSchool();//err
}
class RideBike extends GoSchool{
@Override
public void goSchool() {
System.out.println("骑自行车去上学");;
}
}
class ByCar extends GoSchool{
@Override
public void goSchool() {
System.out.println("坐车去上学");
}
}
public class Test1 {
public static void goSchool(GoSchool goSchool){
goSchool.goSchool();
}
public static void main(String[] args) {
//GoSchool goSchool = new GoSchool();err
goSchool(new RideBike());//打印骑自行车去上学
}
}
上面父类中的我们隐藏的goSchool()不能具体的描述怎样上学,没有实际的意义,不能具体的实现goSchool()方法,都是子类在完成相应的描述,所以方法里面的内容System.out.println("怎样的方式去上学!");是可有可无的。对于这样的方法,我们可以直接将里面的内容省略掉,只留下一个“空壳”,如何来实现呢,下面就来为您解答。
1.2 抽象类的语法
Java中我们使用abstract来修饰这样的方法,称之为抽象方法;含有抽象方法的类我们也要用abstract修饰,叫做抽象类;抽象的方法不用给出具体的实现,只需要一个“空壳”就可以。请看下面的代码:
abstract class GoSchool{
//抽象类的方法直接用分号结尾
public abstract void goSchool();
}
上面的代码段就给我演示如何创建一个抽象类,在抽象类中,除了不能够实例化对象外,其余的功能都是与普通类一样,抽象类也是有构造方法的,用途就是供被继承后,子类用来初始化父类中的成员。
1.3 抽象类的特点与作用
(一)抽象类是不能够直接实例化对象的,否则会出错,只能被继承(抽象类的最大的意义就是用来继承的)。
GoSchool goSchool = new GoSchool();//err
(二)抽象类的方法不能被private、static、final修饰,private只能在当前类中使用,static,final不能被重写(抽象类的方法是要被重写的)。
public static abstract void goSchool();//err
public final abstract void goSchool();//err
private abstract void goSchool();//err
(三)抽象类是必须被继承的,这是他出现的意义,并且继承后子类要重写父类中的抽象方法,如果不想被重写,那么子类也修饰为抽象类,使用 abstract 修饰。但是呢在后面的继承中,如果出现一个普通的类继承,那么普通类就需要重写前面所有抽象类中没有被重写的抽象方法。
(四)没有抽象类方法的类也可以被abstract修饰。
abstract class GoSchool{}
(五)抽象类虽不能实例化对象,但是他可以接收子类(普通类)的对象。
public static void goSchool(GoSchool goSchool){
goSchool.goSchool();
}
public static void main(String[] args) {
goSchool(new RideBike());//打印骑自行车去上学
}
(六)抽象类的作用就是用来校验我们的代码,让我们更早的知道我们的错误,而不是在编译的时候才知道我们写的代码报错了;就比如我们把一串字符串定义为int型,编译器就会马上提示你这个地方写错了,这就是编译器的校验。
int a = "dased";//err
二、接口详解
2.1 接口的概念
信息化的时代,我们已经离不开生活中的接口了,比如电脑、手机:
电脑上有好几个USB的接口,我们可以插入鼠标、键盘、U盘、手机、usb风扇等等。
手机只有一个USB接口,可以用来充电或者导数据等等......
由此我们可以知道:接口就是公共的行为规范标准,只要符合规范标准,就可以通用(就比如上面的手机,只要是能用的数据线,就能给手机充电)。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2.2 接口的实现
下面分几点讲解接口的实现:
//这是第二章节全部的代码
interface IGoSchool{
public static final int a = 10;//与int a = 10;一模一样;public static final是默认的
public abstract void test();//接口当中的方法是不能有具体实现的,都是默认public abstract修饰的,默认为抽象方法
default void defaultTest(){//静态和默认的方法都是默认public修饰的(接口是拿来用的)
System.out.println("接口默认的方法");
}
static void staticTest(){
System.out.println("静态的方法");
}
}
//class ByCar implements IGoSchool{}//不想重写抽象方法
class ByCar implements IGoSchool{
@Override
public void test() {
System.out.println("坐车去学校");
}
//public和default共存了
public void defaultTest(){//静态和默认的方法都是默认public修饰的(接口是拿来用的)
System.out.println("接口默认的方法!!!");
}
}
public class Test {
public static void goSchool(IGoSchool iGoSchool){
iGoSchool.test();//打印坐车去学校
}
public static void main(String[] args) {
//IGoSchool iGoSchool = new ByCar();//发生向上转型,类实现了接口,引用了具体的实现类
goSchool(new ByCar());//
}
}
(一)接口的定义格式和定义类的格式是差不多的,将class换成interfaces即可。
interface IGoSchool
(二)接口是不能直接使用的,必须要一个“实现类”来实现接口,重写接口中的所有抽象方法,雷和接口之间叫做“实现”。
class ByCar implements IGoSchool
(三)接口中的方法是不能有实现的,除非加default。
default void defaultTest(){//静态和默认的方法都是默认public修饰的(接口是拿来用的)
System.out.println("接口默认的方法");
}
(四)重写抽象方法后,要注意子类里面的访问限定符,接口的访问限定符是默认的,子类的访问限定符就不能是默认的了(子类的访问限定符要大于等于父类的)。
(五)抽象方法中是不能有构造方法、实例代码块和静态代码块的。
(六)一个类继承接口,是需要重写接口里面所有的抽象方法的,不然这个类也需要修饰为抽象类
class ByCar implements IGoSchool{}//不想重写抽象方法
(七)接口当中的成员变量默认public static final修饰,成员方法默认public abstract修饰。
public static final int a = 10;//与int a = 10;一模一样;public static final是默认的
public abstract void test();//接口当中的方法是不能有具体实现的,都是默认public abstract修饰的,默认为抽象方法
2.3 多接口的实现
Java中不支持多继承,但是可以有多个接口的存在。为什么会有多接口呢,就动物世界来说鸟会飞,鱼会游泳,老虎会跑等等,那我们就可以吧可以游泳的、能飞的、能上树的归为一个接口,因为一个类中创建是可能被继承的,而且一个类中我们不能把所有动物的生活方式全部写在一起,所以才会出现接口。
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
}
interface IFlying{//能飞的
void fly();
}
interface ISwimming{//能游泳的
void swimming();
}
interface IRunning{//能跑的
void running();
}
//一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
//一个类只能被单继承,但可以实现多个接口。
class Tiger extends Animal implements IRunning,IFlying,ISwimming{
@Override
public void fly() {
System.out.println(name+"我可不会飞!");
}
@Override
public void swimming() {
System.out.println(name+"我可不会游泳!");
}
public Tiger(String name){
super(name);
}
@Override
public void running() {
System.out.println(name+"正在飞快的跑!");
}
}
public class Test {
public static void func(Tiger tiger){
tiger.fly();
tiger.swimming();
tiger.running();
}
public static void main(String[] args) {
func(new Tiger("老虎"));
}
}
🚀接口是用来描述某个类具备某种能力的,程序员写代码的时候,就不必关心具体的类型。
🚀一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
🚀一个类只能被单继承,但可以实现多个接口。
2.4 接口的继承
Java中,不只有类才有继承,接口也是能继承的,就像多态一样,可以实现代码的复用,也是使用extends关键字来发生继承关系,也叫做扩展功能。
interface IFlying{
void fly();
}
interface ISwimming{
void swimming();
}
interface IRunning extends ISwimming,IFlying{
void running();
}
2.5 接口的使用
2.5.1 数组的比较
//public interface Comparable<T> <T>是泛型 T代表类型 Comparable<T>是编译器的接口
//public int compareTo(T o);接口的方法
class Dog implements Comparable<Dog>{
public String name;
public int age;
public double weight;
public Dog(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
@Override
public int compareTo(Dog o) {
if(this.age > o.age)//this表示当前引用dog,o接收dog1
return 1;
else if(this.age == o.age)
return 0;
else
return -1;
//return this.age - o.age;
}
}
//尤拉比较器,就可以删除Comparable<Dog>接口
//名字比较器
class NameComparator implements Comparator<Dog>{//根据用户的需求来定义,不用直接写死
@Override
public int compare(Dog o1, Dog o2) {//实现重写的快键键 按住ALT+回车+回车
return o1.name.compareTo(o2.name);
//return o1.name.length()-o2.name.length();//main3
}
//equals为什么不重写了
}
//年龄比较器
class AgeComparator implements Comparator<Dog>{
@Override
public int compare(Dog o1, Dog o2) {//实现重写的快键键 按住ALT+回车+回车
return o1.age-o2.age;
}
//equals为什么不重写了
}
public class Test1 {
public static void main3(String[] args) {
AgeComparator ageComparator = new AgeComparator();
NameComparator nameComparator = new NameComparator();
Dog dog = new Dog("aaa",3,36.3);
Dog dog1 = new Dog("b",6,66.3);
int ret = ageComparator.compare(dog,dog1);
System.out.println(ret);//打印的是两个age之间的差值:-3
int ret1 = nameComparator.compare(dog,dog1);
System.out.println(ret1);//打印的是两个name长度之间的差值:2
}
public static void main2(String[] args) {
Dog[] dogs = new Dog[2];
dogs[0] = new Dog("哈士奇",3,36.3);
dogs[1]= new Dog("牧羊犬",6,66.3);
Arrays.sort(dogs);//排序的原理是把每一个元素强转成comparable
System.out.println(Arrays.toString(dogs));//如果没有Comparable<Dog>这个接口,那代码会报错
Arrays.sort(dogs,new AgeComparator());
System.out.println(Arrays.toString(dogs));
}
public static void main1(String[] args) {
Dog dog = new Dog("哈士奇",3,36.3);
Dog dog1 = new Dog("牧羊犬",6,66.3);
//比较两个自定义数组之间的大小有两个步骤:1.指定比较的方法2.告诉编译器怎么做
if(dog.compareTo(dog1) < 0)
System.out.println("dog < dog1");
}
}
🚀自定义的数据进行比较时,需要 public interface Comparable<T> ;
🚀比较大小有两个步骤:1.指定比较的方法2.告诉编译器怎么做
2.5.2 比较器
比较器的出现可以根据用户的需求来自定义,使用Comparator<T>接口,上面代码中已有了名字和年龄两个比较器的用法,这里就不做说明了。
2.5.3 克隆
首先我们要实现一个克隆接口,克隆就是产生对象的一个副本,如何来产生呢?实现Cloneable接口,这个接口里面没有抽象方法,所谓空接口(标记接口),意味可以被克隆,需要重写object类的克隆方法。
class Clone implements Cloneable{//空接口:现了这个接口的类可以被克隆
public int a = 0;
@Override
protected Object clone() throws CloneNotSupportedException {//不同包的子类
return super.clone();//调用object类
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {//异常(后面详解)
CClone clone = new Clone();
System.out.println(clone.a);
Clone clone1 = (Clone)clone.clone();//克隆Clone类型的,克隆的返回值是object类,向上转型需要强制类型转换
System.out.println(clone1.a);
}
}
图解克隆:
2.5.4 浅拷贝
什么是浅拷贝呢?就是拷贝的时候,没有拷贝到对象里面所引用的对象(我拷贝的对象里面还存了一份对象的地址,原对象里面存的对象没有被拷贝)下面就来解释:
//为了方便,这里的代码加上上面克隆的代码就是全部的代码
class A{
public int b = 10;
}
class Clone implements Cloneable{//空接口:现了这个接口的类可以被克隆
public int a = 0;
public A c = new A();//存了A对象的地址
}
public class Test1 {
public static void main(String[] args) throws CloneNotSupportedException {
clone.c.b = 20;
System.out.println(clone.c.b);
System.out.println(clone1.c.b);
}
}
图解:
由上面的图和代码可知,A对象没有被拷贝,clone和clone1里面都存了对象A的地址,同时指向了A,当我们任意改变一个clone和clone1里面所存对象A里面b的值时,两个clone和clone1都会被改变,这样的现象就叫做浅拷贝。
2.5.5 深拷贝
有了上面的基础,我们就可以轻易的得出深拷贝的原理了,请看下面的图解:
深拷贝就是只要是被拷贝的,都要单独的占用一份空间,而类A想要被克隆的话,也是要使用克隆接口的,下面的代码是修改后的代码,当我们去任意改变clone或clone1中引用A中的值b的话,两个是互不干扰的:
//照着上面的代码修改
class A implements Cloneable{
public int b = 10;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {//不同包的子类
Clone clone = (Clone)super.clone();//克隆clone变成clone1
clone.c = (A) this.c.clone();//克隆A的对象 (clone在栈区中作为局部变量传递地址)
return clone;
//return super.clone();//调用object类
三、object类详解
class Dog{//默认继承父类object
public String a="asd";
@Override
public String toString() {
return "Dog{" +
"a='" + a + '\'' +
'}';
}
}
class Tiger{}
public class test {
public static void func(Object o){
String ret = o.toString();
System.out.println(ret);
//System.out.println(o);
}
public static void main(String[] args) {
func(new Tiger());//打印demo4.Tiger@1b6d3586 demo4代表的我的包
func(new Dog());//打印Dog{a='asd'}
}
}
上面的代码我们没有手动的去继承,那么所有的类就会默认继承object类。
3.1 equals方法
equals方法就是判断两个对象或者对象里面的值是否相等。
//只有核心的代码块,加上面的代码就是全部
public boolean equals(Object obj){
if(obj == null)//传过来的参数时候为空
return false;
if (this == obj) // 两个对象的地址是否相同
return true;
Dog dog = (Dog) obj;
return this.a.equals(dog.a);
}
public static void main(String[] args) {
Dog dog = new Dog("qwe");
Dog dog1 = new Dog("qwe");;
System.out.println(dog1.equals(null));
//System.out.println(dog.equals(null));//对应上面的第一个if
//Dog dog1 = dog;//对应上面第二个if
}
当我们没有重写equals方法的时候,equals默认是object类的方法。
3.2 hashcode方法
hashcode方法用来确定对象在内存中存储的位置是否相同(数据结构会详解,这里使用编译器生成)
//重写hashCode()
@Override
public int hashCode() {
return Objects.hash(a);
}
public static void main(String[] args) {
Dog dog = new Dog("qwe");
Dog dog1 = new Dog("qwe");;
System.out.println(dog.hashCode());//字符串一样会返回同样的哈希值
System.out.println(dog1.hashCode());
}
总结
抽象类:
1.使用abstract修饰的方法叫做抽象方法,有抽象方法的类,必须为抽象类。(没有抽象方法也可以修饰为抽象类)
2.抽象类不能够实例化,其余与普通的类一样,可以被继承(最大的意义就是被继承)。
3.抽象类被继承后,子类需要重写抽象类的所有抽象方法,不想被重写,可以将子类也修饰为抽象类,但是又被一个不是抽象类继承,那他需要重写所有的抽象方法。
接口:
1.和类的定义格式一样,将class换成interface,接口名的前面加上大写字母I(例如IFlower),看起来更加的规范。
2.接口当中的成员变量都是默认public static final修饰的;成员方法都是public abstract修饰的
3.接口当中的普通方法是不能有具体实现的,除非加上default修饰【从JDK8开始】。
4.接口当中的静态方法和default方法都是public默认修饰的。
5.接口也是不可以进行实例化的。
6.类和接口的关系用implements关联,关联后,必须重写接口里所有的方法。
7.接口可以引用接口具体的实现类,想当于向上转型。
8.不能有实例代码块、静态代码块、构造方法。