1,抽象类
所谓抽象就是不是特指某个对象,是个很宽泛的概念。如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
1.1 抽象类语法
在 Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法。
- 抽象方法不能给具体的实现体
- 如果有抽象方法的类一定是抽象类,但抽象类里面不一定有抽象方法。
java
// 抽象类:被abstract修饰的类
public abstract class Shape {
// 抽象⽅法:被abstract修饰的⽅法,没有⽅法体
abstract public void draw();
abstract void calcArea();
// 抽象类也是类,也可以增加普通⽅法和属性
public double getArea(){
return area;
}
protected double area;// ⾯积
}
抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
1.2 抽象类的特性
-
抽象类无法实例化,抽象类可以帮助构建对象。
-
抽象方法不能被 private,final,static修饰,因为抽象方法要被子类重写
javaabstract class Shape { abstract private void draw(); } public abstract class Shape { abstract final void methodA(); abstract public static void methodB(); } // 编译出错 -
抽象类必须被继承,并且继承后子类要重写父类的抽象方法,子类也是抽象类就不用重写,但是后续有非抽象类继承,需要把所有的抽象方法都重写。
javapackage demo1; public abstract class Animal { abstract void birth(); } package demo1; //哺乳类 public abstract class Mammals extends Animal { abstract void viviparity();//胎生 } package demo1; public class Dog extends Mammals{ //重写两个抽象方法 @Override void viviparity() { System.out.println("胎生"); } @Override void birth() { System.out.println("出生"); } } -
抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量,就和继承的子类创建必须先构造父类一样
1.3 抽象类的作用
抽象类不能实例化,只能继承给其他类帮助其他类创建对象,如果实例化了抽象类就会报错。
2,接口
2.1 接口
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是多个类的公共规范,是一种引用数据类型
2.2 语法规则
接口的定义格式与定义类的格式相同,将class关键字换成 interface 关键字,就定义了一个接口
java
public interface 接⼝名称{
// 抽象⽅法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
// 注意:在接⼝中上述写法都是抽象⽅法,跟推荐⽅式4,代码更简洁
}
- 创建接口时,接口的命名一般以大写字母 i 开头
- 接口的命名一般使用"形容词"词性的单词
- 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号
- 接口当中的成员变量默认是:public static final
2.3 接口的使用
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
类比抽象类
java
public class 类名称 implements 接⼝名称{
// ...
}
类和类是继承 extends 的关系,类和接口是 implements 实现的关系
实现笔记本电脑使⽤ USB ⿏标、 USB 键盘的例子
USB 接⼝:包含打开设备、关闭设备功能
笔记本类:包含开机功能、关机功能、使⽤ USB 设备功能
⿏标类:实现 USB 接⼝,并具备点击功能
键盘类:实现 USB 接⼝,并具备输⼊功能
java
// USB接⼝
public interface USB {
void openDevice();
void closeDevice();
}
// ⿏标类,实现USB接⼝
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开⿏标");
}
@Override
public void closeDevice() {
System.out.println("关闭⿏标");
}
public void click(){
System.out.println("⿏标点击");
}
}
// 键盘类,实现USB接⼝
public class KeyBoard implements USB {
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void inPut(){
System.out.println("键盘输⼊");
}
}
// 笔记本类:使⽤USB设备
public class Computer {
public void powerOn(){
System.out.println("打开笔记本电脑");
}
public void powerOff(){
System.out.println("关闭笔记本电脑");
}
public void useDevice(USB 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 TestUSB {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
// 使⽤⿏标设备
computer.useDevice(new Mouse());
// 使⽤键盘设备
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
2.4 接口特性
1.接口是一种引用类型,但是不能直接new接口的对象
java
public class TestUSB {
public static void main(String[] args) {
USB usb = new USB();
}
}
// Error:(10, 19) java: day20210915.USB
//是抽象的; ⽆法实例化
2.接口中每一个方法都是public的抽象方法,隐式类型为 public abstract (只能是 public abstract,其他修饰符都会报错)
java
public interface USB {
// Error:(4, 18) java: 此处不允许使⽤修饰符private
private void openDevice();
void closeDevice();
}
3.接口中的方法不能有主体,因为是抽象方法,jdk8中:接⼝中还可以包含default⽅法
java
public interface IUSB {
default void closeDevice(){
System.out.println("关闭USB设备");
}
}
4.重写接口中方法时,不能使用默认的访问权限,要带public
java
public interface USB {
void openDevice(); // 默认是public的
void closeDevice(); // 默认是public的
}
public class Mouse implements USB {
@Override
void openDevice() {
System.out.println("打开⿏标");
}
// ...
}
// 编译报错,重写USB中openDevice⽅法时,不能使⽤默认修饰符
// 正在尝试分配更低的访问权限; 以前为public
5.接口中的成员变量默认是 public static final 修饰
java
public interface USB {
double brand = 3.0; // 默认被:final public static修饰
void openDevice();
void closeDevice();
}
public class TestUSB {
public static void main(String[] args) {
System.out.println(USB.brand); // 可以直接通过接⼝名访问,说明是静态的
// 编译报错:Error:(12, 12) java: ⽆法为最终变量brand分配值
USB.brand = 2.0; // 说明brand具有final属性
}
}
6.接口中不能有静态代码块和构造方法
java
public interface USB {
// 编译失败
public USB(){
}
{} // 编译失败
void openDevice();
void closeDevice();
}
7.接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是 .class
8.如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
2.5 实现多接口
我们都知道 Java没有多继承,但是可以用接口实现多继承的效果
java
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
实现多个接口时,每个接口的抽象方法都要实现
IDEA使用 ctrl + i 快速实现接口
2.6 接口与接口的继承
java
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
...
}
接口间的继承相当于把多个接口合并在一起
2.7 接口使用例子
对象之间进行大小关系比较
-
使用 Comparable 接口 让我们的Student类实现Comparable接口,并实现其中的compareTo⽅法
javaclass Student implements Comparable { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } @Override public String toString() { return "[" + this.name + ":" + this.score + "]"; } @Override public int compareTo(Object o) { Student s = (Student)o; if (this.score > s.score) { return -1; } else if (this.score < s.score) { return 1; } else { return 0; } } }javapublic class Test { public static void main(String[] args) { Student s1 = new Student("zhangsan",10); Student s2 = new Student("lisi",20); System.out.println(s1.compareTo(s2) ); } }如果s1⼤于s2那么返回⼤于0的数字,如果相同返回0,否则返回小于0的数字,这种方法写了之后就很难改变了。
-
使用Comparator接口
java
class Student {
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "[" + this.name + ":" + this.score + "]";
}
}
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.score-o2.score;
}
}
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
java
public class Test {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",10);
Student s2 = new Student("lisi",20);
//根据分数进⾏⽐较
ScoreComparator scoreComparator = new ScoreComparator();
System.out.println(scoreComparator.compare(s1, s2));
//根据姓名进⾏⽐较
NameComparator nameComparator = new NameComparator();
System.out.println(nameComparator.compare(s1, s2));
}
}
2.8 Clonable 接口和深拷贝
Object类中存在⼀个clone⽅法,调⽤这个⽅法可以创建⼀个对象的"拷⻉".但是要想合法调⽤clone ⽅法,必须要先实现Clonable接⼝,否则就会抛出CloneNotSupportedException异常
java
class Animal implements Cloneable {
private String name;
@Override
public Animal clone() {
Animal o = null;
//异常捕获的知识
try {
o = (Animal)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
Animal animal2 = animal.clone();
System.out.println(animal == animal2);
}
}
// 输出结果
// false
浅拷贝
java
class Money {
public double m = 99.99;
}
class Person implements Cloneable{
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo3 {
public static void main(String[] args) throws
CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println("通过person2修改前的结果");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
person2.money.m = 13.6;
System.out.println("通过person2修改后的结果");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
}
}
// 执⾏结果
通过person2修改前的结果
99.99
99.99
通过person2修改后的结果
13.6
13.6
深拷贝
java
class Money implements Cloneable{
public double m = 99.99;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.money = (Money) this.money.clone();
return tmp;
}
}
public class TestDemo3 {
public static void main(String[] args) throws
CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println("通过person2修改前的结果");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
person2.money.m = 13.6;
System.out.println("通过person2修改后的结果");
System.out.println(person1.money.m);
System.out.println(person2.money.m);
}
}
2.9 抽象类和接口的区别
相同点
- 都不能实例化
不同点
- 结构组成:抽象类:普通类+抽象方法。接口:抽象方法+全局常量
- 权限:抽象类:各种权限。接口:public
- 子类使用:抽象类:extends继承。接口:implements实现
- 关系:抽象类:可以实现多个接口。接口:可以继承多个父类接口
- 子类限制:抽象类:只能继承一个子类。接口:可以继承多个接口
3.内部类
将一个类定义在另一个类或者方法的内部,叫做内部类
java
public class OutClass {
class InnerClass{
}
}
// OutClass是外部类
// InnerClass是内部类
内部类和外部类共用一个Java源文件,但是经过编译之后,内部类会形成单独的字节码文件
3.1 内部类的分类
- 静态内部类
- 实例内部类
- 局部内部类
- 匿名内部类
静态内部类
被 static 修饰的内部成员类称为静态内部类
java
public class OutClass {
public int a;
public static int b;
// 静态内部类:被static修饰的成员内部类
static class InnerClass{
public void methodInner(){
// 在内部类中只能访问外部类的静态成员
// a = 100; // 编译失败,因为a不是类成员变量
b =200;
}
}
public static void main(String[] args) {
// 静态内部类对象创建 & 成员访问
OutClass.InnerClass innerClass = new OutClass.InnerClass();
innerClass.methodInner();
}
}
- 在静态内部类中只能访问外部类中的静态成员
- 创建静态内部对象时,不需要先创建外部类对象
实例内部类
未被 strtic 修饰的成员内部类
java
public class OutClass {
public int a;
public static int b;
public int c;
// 实例内部类:未被static修饰
class InnerClass{
int c;
public void methodInner(){
// 在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
a = 100;
b =200;
// 如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类⾃⼰的
c = 300;
System.out.println(c);
// 如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
OutClass.this.c = 400;
System.out.println(OutClass.this.c);
}
}
java
public static void main(String[] args) {
// 外部类:对象创建以及成员访问
OutClass outClass = new OutClass();
System.out.println(outClass.a);
System.out.println(OutClass.b);
System.out.println(outClass.c);
System.out.println("=============实例内部类的访问=============");
// 要访问实例内部类中成员,必须要创建实例内部类的对象
// ⽽普通内部类定义与外部类成员定义位置相同,
//因此创建实例内部类对象时必须借助外部类
// 创建实例内部类对象
OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
// 上述语法⽐较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象
OutClass.InnerClass innerClass2 = outClass.new InnerClass();
innerClass2.methodInner();
}
}
- 外部类中的任何成员都可以在实例内部类方法中直接访问
- 实例内部类所处的位置与外部类成员位置相同,因此也受public,private等访问限定符的约束
- 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须使用:外部类名称.this.同名成员 来访问
- 实例内部类对象必须在先有外部对象的前提下才能创建
- 实例内部类的非静态方法中包含了一个指向外部类对象的引用
- 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使⽤,⼀般使用的⾮常少
java
public class OutClass {
int a = 10;
public void method(){
int b = 10;
// 局部内部类:定义在⽅法体内部
// 不能被public、static等访问限定符修饰
class InnerClass{
public void methodInnerClass(){
System.out.println(a);
System.out.println(b);
}
}
// 只能在该⽅法体内部使⽤,其他位置都不能⽤
InnerClass innerClass = new InnerClass();
innerClass.methodInnerClass();
}
public static void main(String[] args) {
// OutClass.InnerClass innerClass = null; 编译失败
}
}
- 局部内部类只能在所定义的方法体内部使用
- 不能被public,static等修饰
- 编译器也有自己独立的字节码文件,命名格式:外部类$数字内部类名字.class
- 几乎不会使用
匿名内部类
没有类名的内部类
java
new SuperType(constructor-arguments) {
//类体
};
SuperType可以是接口,抽象类或具体类
java
interface Greeting {
void greet();
}
public class Test {
public static void main(String[] args) {
Greeting greeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hello!");
}
};
greeting.greet();
}
}
匿名内部类当中可以定义和正常类一样的成员变量,但是和正常类一样都不能直接包含执行语句
4.Object类
object是祖先类,一般不写继承的都继承object类
使用Object接受所有类的对象
java
class Person{}
class Student{}
public class Test {
public static void main(String[] args) {
function(new Person());
function(new Student());
}
public static void function(Object obj) {
System.out.println(obj);
}
}
//执⾏结果:
Person@1b6d3586
Student@4554617c
发生了向上转型
4.1 获取对象信息
如果要打印对象中的内容,就可以直接重写Object中的toString()方法
java
// Object类中的toString()⽅法实现:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
4.2 对象比较equals方法
比较相同或者不同
Java中比较相同:
- 如果==左右两侧是基本类型变量,比较的是变量中值是否相同
- 如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
- 要比较就要重写Object中的equals方法,因为equals方法默认也是按照地址比较的
java
// Object类中的equals⽅法
public boolean equals(Object obj) {
return (this == obj); // 使⽤引⽤中的地址直接来进⾏⽐较
}
java
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.age = age;
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("cyy", 20);
Person p2 = new Person("cyy", 20);
int a = 10;
int b = 10;
System.out.println(a == b); // 输出true
System.out.println(p1 == p2); // 输出false
System.out.println(p1.equals(p2)); // 输出false
}
}
重写equals方法
java
class Person{
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false ;
}
if(this == obj) {
return true ;
}
// 不是Person类对象
if (!(obj instanceof Person)) {
return false ;
}
Person person = (Person) obj ; // 向下转型,⽐较属性值
return this.name.equals(person.name) && this.age==person.age ;
}
}
4.3 hashcode方法
hashcode方法源码
java
public native int hashCode();
该方法是一个native方法,底层是由C/C++代码写的看不到。
我们认为两个名字相同,年龄相同的对象,将存储在同⼀个位置,如果不重写hashcode()方法,我们求出的hash值不一样
重写hashcode()方法
java
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 TestDemo4 {
public static void main(String[] args) {
Person per1 = new Person("cyy", 20) ;
Person per2 = new Person("cyy", 20) ;
System.out.println(per1.hashCode());
System.out.println(per2.hashCode());
}
}
//执⾏结果
//3070322
//3070322
hashcode方法用来确定对象在内存中存储的位置是否相同