1.抽象类
1.1 抽象类的概念
前文讲过的普通的类,就是现实事物在 Java 代码中的一种 “抽象的表现形式”,类里包含着属性,方法。
抽象方法:将方法的实现部分忽略掉,只保留方法的名字,参数等信息(没有方法的实现逻辑)
包含抽象方法的类,就称为抽象类
抽象类的存在,更多是为了做出 “强制限制”,与 final 类似。
1.2 抽象类的语法
abstarct : 通过这个关键字修饰的方法,就叫做抽象方法;
通过这个关键字修饰的类,就叫做抽象类。
//抽象类
public abstract class Shape {
//抽象方法(不能写具体的实现方法)
public abstract void draw();
//普通属性
private double area;
//普通方法
public double getArea(){
return area;
}
//静态属性
private static int n = 0;
//静态方法
public static int getCount(){
return n;
}
}
这里写的抽象方法就是为了交给子类来重写
1.3 抽象类的特性
1、一个抽象类中可以包含普通的属性和方法,也可以包含抽象方法
2、有了抽象方法就一定要有抽象类
3、抽象类不可以被 private 和 static 修饰
4、当父类提供了抽象方法后,子类必须重写这个抽象方法,或者把这个子类也修饰成抽象方法
5、抽象类不能直接实例化对象(抽象类自身不能表示一个“完整的概念”)
6、抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量(构造方法,不一定是在 new 自身的时候触发,new 子类实例也是能触发父类的构造的)
public Shape(){
area = 100;
System.out.println("父类的构造方法");
}
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
}
}
2.接口(interface)
2.1 接口的定义
接口的定义非常广泛,不局限于 Java
1、在C语言中,一个函数,也可以称为一个接口
2、Java 中的 interface 也是接口
3、工作中,把一个大的项目,分成多个模块,把模块之间,相互调用的部分,也称为接口
4、服务器,能够处理各种请求的,称为“服务器提供的接口”
5、图形化界面(GUI)也是接口
..........
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2.2 接口的定义
接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface 关键字,就定义了一个接口
public interface 接口名称 {
}
对于接口来说,里面不能包含属性,包含的方法也只能是抽象方法
[public abstract] void draw();
在接口内部定义的抽象方法可以省略 pubile 和 abstract ,这两个关键字都是被默认添加的
提示(不强制要求):
- 创建接口时, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 "形容词" 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
从Java8开始允许往 interface 中放属性,但是有严格要求,会在属性前默认添加 public static final
2.3 接口的使用
和抽象类类似,接口也是不能直接实例化的,要想使用接口,就需要创建类,“实现” 接口
对于实现接口的类来说,接没有什么可以继承的,没有可以重用的内容,而且需要严格按照接口中提供的抽象方法在子类中进行重写
public class 实现类 implements 接口{
.......
}
代码示例:
public interface USB {
//打开
void openDevice();
//关闭
void closeDevice();
}
public class Mouse implements USB{
@Override
public void openDevice() {
System.out.println("插入鼠标");
}
@Override
public void closeDevice() {
System.out.println("拔出鼠标");
}
}
public class Test {
public static void main(String[] args) {
USB usb = new Mouse();
usb.openDevice();
usb.closeDevice();
}
}
这里虽然不使用 “继承术语”,此处仍然视为 “向上转型”
调用下面两个方法时也可以触发 “多态”
2.4 接口特性
对前面内容进行一个小结
1、接口类型是一种 引用类型,但是不能直接 new 接口的对象(比抽象类更抽象)
2、接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
3、接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
4、重写接口中方法时,不能使用默认的访问权限
5、接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
6、接口中不能有静态代码块和构造方法
7、接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
8、如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
9、jdk8中:接口中还可以包含default方法。
2.5 实现多个接口
接口和抽象类都,都提供了抽象方法
直观上感觉,抽象类功能更强一些,接口限制更严格
Java 的继承体系是 “单继承” 体系
抽象类意味着,该类只能继承自一个抽象类,但是一个类可以实现多个接口
//定义一个接口 表示会跑的
public interface IRunning {
void run();
}
//定义一个父类,用来表示动物和动物的名字
public class Animal {
private String name;
public Animal(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//定义子类,继承父类实现 IRunning 接口
// Cat 是会跑的 (具有xxx特性)
public class Cat extends Animal implements IRunning{
public Cat(String name){
super(name);
}
public void run(){
System.out.println(getName() + "使用四条腿跑");
}
}
//最后在 Test 类中进行测试
public class Test {
public static void main(String[] args) {
Animal animal = new Cat("小猫");
System.out.println(animal.getName());
//通过不同的实例化,进行对应的操作
IRunning iRunning = new Cat("小猫");
iRunning.run();
}
}
最后的 Test 的类也可以写成
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小猫");
System.out.println(cat.getName());
cat.run();
}
}
可以同时对两个方法进行调用
当要实现多个接口,使用 “,” 将接口分隔开,
public class 子类 extends 父类 implements 接口1,接口2,...,接口n{
......
}
最终 类 里要把所有接口里的抽象方法都实现出来
public class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name) {
super(name);
}
@Override
public void run() {
}
@Override
public void swim() {
}
}
2.6接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。(把多个小的接口合并成一个大的接口)
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字
这里仍然以上面代码为例
//定义一个两栖(既可以跑也可以游泳的动物)类,直接继承前面写的 IRunning,ISwimming
public interface IAmphibious extends IRunning,ISwimming{
}
有了这个接口,青蛙类就可以修改为
public class Frog extends Animal implements IAmphibious{
public Frog(String name) {
super(name);
}
@Override
public void run() {
}
@Override
public void swim() {
}
}
在接口拆的比较零碎的时候,这种写法可读性,易写性都会更高
2.7接口使用实例
2.7.1 Comparable 接口
对象之间进行大小关系比较
有些属性不能直接使用 > < 来进行比较,但是比较的需求是客观存在的,所以需要明确一个比较规则,再进行比较。
Java 标准库中比较规则的指定内置了一个接口 Comparable
后面跟着的尖括号是 泛型参数 涉及到数据结构这里不作介绍,只需要保证前后一致即可
在这里接口里就提供了一个 compareTo 的方法
@Override
public int compareTo(Student o) {
return 0;
}
这个方法是一个实例方法,内涵了 this,当前对象,参数还有一个 o ,表示另一个要比较的对象,这里就是 this 和 o (other)来记性比较
如果 this 比 o 小,返回 < 0 的整数 ;
如果 this 比 o 大,返回 > 0 的整数 ;
如果 this 和 o 相等,返回 0
如果希望先比较学生的成绩,成绩高的就是大
如果成绩相同就比较学生的 id ,学生的名字不影响
public static void main(String[] args) {
Student s1 = new Student(1,"张三",90.0);
Student s2 = new Student(2,"李四",80.0);
System.out.println(s1.compareTo(s2));
}
@Override
public int compareTo(Student o) {
if(this.score < o.score){
return -1;
}else if(this.score > o.score){
return 1;
}else {
if(this.id < o.id){
return -1;
} else if (this.id > o.id) {
return 1;
}else {
return 0;
}
}
}
就可以通过返回值来进行判断大小
System.out.println(s1.compareTo(s2));
这里是我们手动调用的 compareTo 方法,而不是使用的 < >。
在 Java 中不支持运算符重载的写法
2.7.2 Comparator 接口
标准库中除了 Comparable,还提供了另一个接口, Comparator 也能进行比较操作,Comparator 是通过创建一个单独的类,来实现 Comparator 接口
import java.util.Comparator;
public class StudentComparator implements Comparator<Student2> {
@Override
public int compare(Student2 o1, Student2 o2) {
return 0;
}
}
Comparator 是拿着两个参数来进行比较
返回值规则与 Comparable 类似
import java.util.Comparator;
public class StudentComparator implements Comparator<Student2> {
@Override
public int compare(Student2 o1, Student2 o2) {
if(o1.getScore() > o2.getScore()){
return 1;
}
if (o1.getScore() < o2.getScore() ) {
return -1;
}
if(o1.getId() < o2.getId()){
return -1;
}
if(o1.getId() > o2.getId()){
return 1;
}
return 0;
}
}
public class Student2 {
private int id; //学号
private String name; //姓名
private double score; //成绩
public Student2 (int id, String name, double score) {
this.id = id;
this.name = name;
this.score = score;
}
public int getId() {
return id;
}
public double getScore() {
return score;
}
public static void main(String[] args) {
Student2 s1 = new Student2(1,"张三",90.0);
Student2 s2 = new Student2(2,"李四",80.0);
StudentComparator comparator = new StudentComparator();
System.out.println(comparator.compare(s1,s2));
}
}
当比较规则有多种,或者被比较的类的源码不能修改
就可以使用 Comparator 了。
2.7.3 Cloneable 接口
允许对象进行克隆
public class Test {
public static void main(String[] args) {
Student s = new Student(1,"张三",90);
Student s2 = s;
}
}
这段代码只是创建一个新的引用,此时 对象 本身仍然是只有一个
克隆 是创建一个新对象,新对象的内容要和旧对象的一样
实现接口
public class 类名 implements Cloneable{
....
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
此处 父类的 clone 方法,不是 Cloneable 本身提供的,而是 Object 这个类提供的
当前写 Student 看起来没有父类,其实隐式继承自 Student
这里有一个 native 关键字,标记这个关键字的方法,称为 “本地方法”,也就是在 JVM 内部,通过 C++ 代码来实现的逻辑,在 Java 中的很多对象,往底层深究,大部分会进入到 C++ 逻辑中
如果希望使用就需要对这段代码进行一个修改
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
修改了返回值类型,只需要与原类型相容即可,再在下面进行强制转换
有了这个克隆方法就可以在下面进行调用
这里会出现一个报错,这是因为clone 方法后跟着一个异常声明,这里不作介绍
只需要对着 s.clone() 按 alt + 回车,选择第一个,把异常声明加到 main 后就可以了,这时我们就得到了一个新的对象,通过 s2 来指向这个对象
public class Student implements Cloneable{
private int id; //学号
private String name; //姓名
private double score; //成绩
public Student(int id, String name, double score) {
this.id = id;
this.name = name;
this.score = score;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public double getScore() {
return score;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setScore(double score) {
this.score = score;
}
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Student s = new Student(1,"张三",90);
Student s2 = s.clone();
System.out.println(s2.id);
System.out.println(s2.name);
System.out.println(s2.score);
//可以使用 == 来判断两个对象地址是否一样
System.out.println(s == s2);
}
}
运行结果
虽然 clone 是 Object 提供的,不是 Cloneable 接口提供的,仍然需要实现 Cloneable 接口,才能使用 Clone 方法(JVM 的硬性规定)
2.8 深拷贝与浅拷贝
clone 操作是否要递归进行:是,就是 深拷贝,不是,就是 浅拷贝
(如果类中的属性都是内置类型,不涉及深浅拷贝问题)
public class Money {
private double amount = 1000;
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
}
public class Person implements Cloneable{
private int id;
private Money money = new Money();
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person();
p1.id = 1;
p1.money.setAmount((1000));
Person p2 = p1.clone();
System.out.println(p2.id);
System.out.println(p2.money.getAmount());
}
}
这里通过 2.7.3 的方式 将 p1 拷贝到了 p2
接下来修改一下 p2 的 id
可以看到结果是分开的,再接着修改一下 money
结果却变成一样的了,这是因为拷贝操作,只是把 Person 对象拷贝了一下
而 Person 内部有的 Money 对象没有真正拷贝
这就我们就叫做 浅拷贝
深拷贝,则是表示,不光要把 Person 对象本身进行拷贝,也要把 Person 内部持有的引用类型的属性,也去进行 拷贝。如果引用类型属性中还有引用类型,就要持续的递归拷贝进去,保证所有内部持有的引用类型,都对应拷贝一份新的
//Person 中的 clone 方法
@Override
public Person clone() throws CloneNotSupportedException {
//这里不着急返回,先把拷贝的值记录下来赋值给 p
Person p = (Person) super.clone();
//再利用 money 的引用类型去拷贝出 money 的值
p.money = p.money.clone();
p;
}
//在 Money 中添加 clone 方法
public Money clone() throws CloneNotSupportedException {
return (Money) super.clone();
}
这样就是深拷贝了
此处 Person 中只有一个引用类型的成员,如果 Person 还有其他引用类型,有几个就要 clone几个
包括 Money 内部,可以也有一些引用类型的成员,也是需要对应 clone。
2.9 抽象类与接口的区别
3.内部类
把一个类定义到另一个内里面(少见的情况)
3.1 普通内部类
几乎见不到,主要介绍语法规则
//最普通的,把类直接写到 文件 中,也可以称为 "外部类"
public class Test {
//类里可以有属性的和方法
private int n = 10;
public void printN(){
System.out.println(n);
}
//正确写法:
public void func(){
Inner inner = new Inner();
inner.printM();
}
//这是一个普通的内部类
//普通内部的创建实例时依赖外部类的 this 引用
//内部类前不能写 public
class Inner{
private int m = 20;
public void printM(){
System.out.println(m);
//内部类可以访问外部类的属性,即使是被 private 修饰
//也可以访问外部类的方法
//这也是内部类的实例需要外部类的 this
System.out.println(n);
}
}
public static void main(String[] args) {
//创建内部类的实例
//此处不能这样写,static 方法中没有 外部类的 this
//Test.Inner inner = new Test.Inner();
Test test = new Test();
test.func();
}
}
3.2 静态内部类
(内部类前面添加 static 关键字)
同样是不常见
public class Test2 {
private int n = 10;
private static int staticN = 100;
//静态内部类
static class Inner{
private int m = 10;
public void printM(){
System.out.println("m="+ m);
//静态内部类无法访问非静态的属性
//编译错误(System.out.println("n+",n);)
//静态成员是可以访问的
System.out.println("staticN = " + staticN);
}
public static void main(String[] args) {
//此时可以创建这个类的实例
Inner inner = new Inner();
inner.printM();
}
}
}
3.3 局部内部类
(方法里面定义的类)
不像是人能写出来的
public class Test3 {
public void func(){
//方法执行到时,才会创建这个类
class Inner{
private int m = 10;
public void printM(){
System.out.println("m=" + m);
}
}
Inner inner = new Inner();
inner.printM();
}
}
3.4 匿名内部类(最重要的)
(局部内部类的特殊情况)
匿名内部类的写法
import java.util.Comparator;
public class Test4 {
public static void main(String[] args) {
//需要给 Test4 这个类,创建 Comparator 比较器
Comparator<Test4> comparator = new Comparator<Test4>() {
@Override
public int compare(Test4 o1, Test4 o2) {
return 0;
}
};
Test4 t1 = new Test4();
Test4 t2 = new Test4();
int ret = comparator.compare(t1,t2);
System.out.println(ret);
}
}
4.Object 类
标准库中内置的类,所有 Java 类的祖宗
所有的 Java 的类都是 直接/间接 继承自 Object
Object 里面就提供了所有 Java 所以类共性的属性方法
4.1 获取对象信息
如果要打印对象中的内容,可以直接重写Object类中的toString()方法,在【类和对象】中已经介绍过,这里不再重复
4.2 对象比较equals方法
== 针对内置类型,确实是比较相等
对于引用类型,是比较两个引用是否指向同一个对象(引用内部存储的地址)
equals 就是给所有类都提供的 自定义的 比较规则
比较两个字符串就需要用到 equals
public class Test {
public static void main(String[] args) {
// String s1 = "hello";
// String s2 = "hello";
// //针对上述这样的两个 String 对象,他们的地址值时相同的
// //即 s1 和 s2 指向同一块内存空间,因此输出True
// System.out.println(s1==s2);
//换一种写法
String s1 = new String("hello");
String s2 = new String("hello");
//这是比较两个引用的指向是否是相同的
System.out.println(s1 == s2);
//比较内容,就可以使用 equals 方法
System.out.println(s1.equals(s2));
}
}
如果是自己定义的类,就需要对 equals 方法进行重写(Student 类的定义可以参考2.7.2中的定义)
@Override
public boolean equals(Object o) {
//比较特殊情况
if(o == null){
// this 肯定不是 null
return false;
}
if(this == o){
//如果两个引用指向一个对象,值肯定是相等的
return true;
}
if(!(o instanceof Student)){
//传入的参数类型和当前 Student 不匹配
return false;
}
//比较两个学生的学号是否相同
//对 o 进行强转
Student other = (Student) o;
return this.id == other.id;
}