(一)抽象类
概念
当这个类不能被具体表达某个对象时,我们把这个类定义为抽象类,被abstract修饰。
//抽象类:被abstract修饰的类
public abstract class Shape{
//抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
//抽象类也是类,也可以增加普通方法和属性
public double getArea(){
return area;
}
protected double area;
}
特性
- 被abstract修饰的方法,叫做抽象方法,没有具体的实现的
- 如果一个类包含了这个抽象方法,此时这个类也必须得用abstract修饰。这个类也被叫做抽象类
- 抽象类必须被继承
- 抽象类当中,可以有和普通类当中一样的成员变量和成员方法
- 不能被实例化 (存在构造方法,因为可以被子类继承后,调用这个构造方法初始化抽象类的成员)👇
- 当一个普通类继承了这个抽象类,那么这个普通类必须重写抽象方法
- 抽象方法不能被private,final,static修饰
- 当一个普通类继承抽象类后,必须要重写抽象类的抽象方法和基类的抽象方法👇
作用
抽象类本身不能实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。那么,普通类可以被继承,普通方法也可以被重写,为啥非要用抽象类和抽象方法?
📍抽象类相当于多了一重编译器的校验
使用抽象类在实际工作中不应由父类完成,而应由子类完成,如果此时不小心误用成父类,使用普通编译器是不会报错的,但是父类是抽象类就会在实例化时提示错误,提醒我们的程序猿。(很多语法存在的意义时为了"预防出错")
(二)接口
概念
被interface修饰的,是一种引用数据类型。它是一种标准规范,多个类的公共规范。
创建接口时,接口的命名一般以大写字母I开头;一般使用形容词词性的单词。
public interface 接口名称{
//抽象方法
public abstract void method();//默认是abstract public,可以不写
public void method2();
abstract void method3();
void method4();//更推荐这种写法
}
接口不能直接使用,必须有一个”实现类“来”实现“该接口,实现接口中的所有抽象方法。
子类和父类之间是extends继承关系,类和接口之间是implements实现关系。
public class 类名 implements 接口名称{
//...
}
特性
- 定义接口用关键字interface定义
- 接口当中的方法,如果没有被实现,那么默认是一个抽象方法
- 接口当中的方法不能有具体的实现(不能在接口中实现,只能由实现接口的类来实现)
- 如果非要有具体的实现,要用default或static修饰
- 接口当中定义成员变量,默认都是public static final的(“常量”)
- 接口当中的抽象方法,默认都是public abstract修饰
- 接口不可以被实例化
- 类和接口之间的关系,可以使用implements来关联
- 接口也可以产生字节码文件的
- 一个类可以继承一个抽象类/普通类,同时还可以实现这个接口(顺序:先继承,再实现)
注意:
接口和抽象类都可以发生向上转型的。
接口使用
(1)
IDEA中,ctrl + i 可以快速生成接口
首先创建一个demo3的包,把所有的类放到该包底下
IUSB接口:
public interface IUSB {
void openDevice();
void closeDevice();
}
Keyboard类,实现IUSB接口
public class KeyBoard implements IUSB{
@Override
public void openDevice() {
System.out.println("打开键盘!");
}
public void inPut() {
System.out.println("键盘输入");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘!");
}
}
Mouse类,实现IUSB接口:
public class Mouse implements IUSB{
@Override
public void openDevice() {
System.out.println("打开鼠标!");
}
public void click() {
System.out.println("点击鼠标!");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标!");
}
}
笔记本类,使用IUSB设备:
public class Computer {
public void powerOn() {
System.out.println("打开电脑!");
}
public void powerOff() {
System.out.println("关闭电脑!");
}
public void useDevice(IUSB iusb) {
iusb.openDevice();
if(iusb instanceof Mouse) {
Mouse mouse = (Mouse)iusb;//向下转型
mouse.click();
}else if(iusb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard)iusb;
keyBoard.inPut();
}
iusb.closeDevice();
}
}
Test类的main方法中:
public class Test {
public static void main(String[] args) {
Computer computer =new Computer();
computer.powerOn();
computer.useDevice(new Mouse());
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
运行结果:
(2)实现多个接口
第十点,还可以补充 👉
一个类实现多个接口时,每个接口中的抽象方法都要实现,否则必须设为抽象类。
public class 子类类名 extends 父类类名 implements A,B,C,D{
//...
}// 此时的A,B,C,D都是接口,中间用英文逗号隔开
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。
当他实现接口后,必须重写里面的抽象方法。
继承表达的含义是is-a的关系,而接口表达的是has-a的关系.
而设置接口的好处就在于,时刻牢记多态的好处,让程序猿忘记类型。有了接口之后,类的使用者就不必再关注具体类型,而只关注某个类是否具备某种能力。
(3)接口间的继承
在Java中,类和类之间是单继承,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
使用extends关键词,当接口A拓展了接口B之后,就可以拥有B中的方法。
interface IRuning {
void run();
}
interface ISwimming {
void swim();
}
//两栖动物,既能游,也能跑
interface IAmphibious extends ISwimming,IRuning{
}
class Frog implements IAmphibious {
}
通过接口继承创建一个新的接口IAmphibious表示”两栖“的,此时实现接口创建的Frog类,就继续要实现run、swim方法。
接口和抽象类的区别
- 抽象类当中,可以包含和普通类一样的成员变量和成员方法,但是接口当中的成员变量只能是public 是static final的,方法只能是public abstract的
- 一个类只能继承一个抽象类,但可以同时实现多个接口,所以解决了Java当中不能进行多继承的特性
(三)内部类
(1)概念
当一个事物的内部,还有一部分需要有一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在Java中,可以将一个类定义在另一个类或一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
上图,火车可以看作一个外部类,车厢看作一个内部类。
注意:
1.定义在class{}花括号外部的,即使在同一个文件,也不能称为内部类
2.内部类和外部类共用同一个Java源文件,经过编译后,内部类会形成单独的字节码文件-->外部类名$内部类类名.class
(2)分类
静态内部类
语法规则:
public class OutClass {
static class InnerClass { };//被static修饰的成员内部类
}
实例:
class OutClass {
private int a;
static int b;
public void methodA() {
a = 10;
System.out.println(a);
}
public static void methodB() {
System.out.println(b);
}
//静态内部类
static class InnerClass {
//在内部类中只能访问外部类的静态成员
public void methodinner() {
// a = 100;//error a不是类的成员变量
b = 200;
// methodA();//error 它不是类的成员方法
methodB();
}
}
public static void main(String[] args) {
OutClass.InnerClass innerClass = new OutClass.InnerClass();//获取静态内部类对象 点好优先级最高,先跟后面结合
innerClass.methodinner();//成员访问
}
}
在静态内部类中只能访问外部类中的静态成员
创建静态内部类对象时,不需要先创建外部类对象
实例内部类
语法规则:
public class OutClass {
public class InnerClass { };//未被static修饰的成员内部类
}
实例:
class OutClass2{
private int a = 2;
static int b = 3;
int c = 4;
public void methodA() {
a = 10;
System.out.println(a);
}
public static void methodB() {
System.out.println(b);
}
//实例内部类
class InnerClass {
int c = 5;
public static final int d = 6;//在实例内部类中 被static修饰的成员变量要加 final并且赋值,这时候把它看作是一个常量。常量是在编译的时候确定的
public void methodinner() {
//在实例内部类中可以直接访问外部类中任意访问限定符修饰的成员
a = 100;
b = 200;
methodA();
methodB();
//如果外部类和实例内部类有相同名字的成员,优先访问内部类自己的
System.out.println(c);
//如果要访问外部类同名成员,必须:外部类名.this.同名的成员名
OutClass2.this.c = 400;
System.out.println(OutClass2.this.c);
}
}
public static void main(String[] args) {
OutClass2 outClass2 = new OutClass2();
System.out.println(outClass2.a);
System.out.println(OutClass2.b);
System.out.println(outClass2.c);
outClass2.methodA();
outClass2.methodB();
//创建实例内部类对象
OutClass2.InnerClass innerClass = new OutClass2().new InnerClass();
innerClass.methodinner();
}
}
实例内部类当中,有两个this
1.实例内部类自己的this
2.外部类的this
匿名内部类
首先,什么是匿名对象?👇
匿名内部类也会生成自己的字节码文件。在以后的学习中更多是要学会使用匿名内部类
(四)Object类
Object类是所有类的父类。它包含以下的方法。
当我们写好一个类Person,其实它默认就是继承了父类Object。并且Object类也可以发生向上转型。
(1)toString方法
(2)对象比较equals方法
在Java中,==进行比较时:
1.如果==左右两侧是基本类型变量,比较的是变量当中值是否相同
2.如果==左右两侧是引用类型变量,比较的是变量当中地址是否相同
*3.如果要比较对象中内容·,必须要重写Object中的equals方法,因为equals方法默认也是按照地址比较的(Object中)👇
main方法中:
public static void main(String[] args) {
Person person1 = new Person("zhangsan",18);
Person person2 = new Person("zhangsan",18);
int a = 10;
int b = 10;
System.out.println(a == b);//true
System.out.println(person1 == person2);//false 直接用 == 比较,比较的是地址
System.out.println(person1.equals(person2));//true 重写equals方法了
}
Person类重写了Object的equals方法:
class Person {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;//不是person类对象
Person person = (Person) o;//向下转型,比较属性值
// return age == person.age && Objects.equals(name, person.name);
return this.name.equals(person.name) && this.age == person.age;//这样写也可以
}
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
}
(3)hashcode方法
toString源码:
hashcode()这个方法,帮我们算了一个具体的对象位置,因为涉及到数据结构,这里只能简单的理解为它是个内存地址,然后调用Integer.toHexString()方法,将这个地址以16进制输出。
hashcode源码:
该方法是一个native方法,底层是由C/C++实现的,我们无法清楚看到。
我们认为,两个名字相同,年龄相同的对象将存储在同一个位置,但是如果不重写hashcode方法,那么两个对象的hash值是不一样的。
因此,也需要在Person类中重写hashcode()方法:
main方法调用
System.out.println(person1.hashCode()); System.out.println(person2.hashCode());
输出结果 哈希值一样:
寄语:不要忘了你希望的远方~