Java基础——抽象类和接口
一、抽象类(强制子类覆写方法)
抽象类是普通类的“超集”,只是比普通类多了一些抽象方法而已(抽象方法个数:0-N)
抽象方法所在的类必须是抽象类,子类若是继承了抽象类,必须覆写所有的抽象方法(子类是普通类)
Java中定义抽象类或者抽象方法使用 abstract 关键字
1、抽象方法所在的类必须使用abstract声明为抽象类
抽象方法指的是1、使用abstract关键字声明,只有函数声明,没有函数实现的方法(2、没有方法体的方法),称为抽象方法。抽象类中没有具体实现,延迟到子类实现
public abstract class Sharp {
public abstract void print();
}
public class Cycle extends Sharp{
@Override
public void print() {
System.out.println("●");
}
}
public class Square extends Sharp{
@Override
public void print() {
System.out.println("■");
}
}
public class Test {
public static void test(Sharp sharp){
sharp.print();
}
public static void main(String[] args) {
test(new Cycle());
test(new Square());
}
}
子类必须覆写所有的抽象方法,否则就会报错
2、若一个类使用abstract关键字声明为抽象类,无法直接通过该类实例化对象,哪怕该类中一个抽象方法都没有。(当一个类是抽象类,不管有无抽象方法,该类本身就是一个抽象的概念,没法具体到某个特定的实例)只能通过子类向上转型变为抽象父类的引用。
3、子类继承了抽象类,就必须强制子类覆写抽象类中的所有抽象方法(子类是普通类)。也满足单继承局限,一个子类只能extends一个抽象类。
abstract class A{
public abstract void printA();
}
//抽象类B继承抽象类A
//A中的抽象方法,B可以覆写,也可以不覆写
//如果B覆写A的抽象方法,则C就不需要再继续覆写A的抽象方法
//如果B没有覆写A的抽象方法,则C既需要覆写B的抽象方法,也需要覆写A的抽象方法
abstract class B extends A{
public abstract void printB();
}
public class C extends B{
@Override
public void printA() {
}
@Override
public void printB() {
}
}
4、抽象类是普通类的“超集”(普通类有的内容,抽象类全都有),只是比普通类多了一些抽象方法而已。抽象类虽然没法直接实例化对象,但也存在构造方法,子类在实例化时,仍然遵从继承的规则,先调用父类的构造方法,而后调用子类的构造方法。
abstract class BaseTest{
public BaseTest(){
print();
}
abstract void print();
}
public class Fun extends BaseTest{
private int num = 10;
@Override
void print() {
//num = 0;
System.out.println("num = " + num);
}
public static void main(String[] args) {
Fun fun = new Fun();
}
}
二、接口
接口中只有全局常量(1%)和抽象方法(99%)—>更加纯粹的抽象概念。
接口使用关键字 interface 声明接口,子类使用 implements 实现接口(必须覆写所有的抽象方法)
1、接口使用的场景
(1)接口表示具备某种能力/行为,子类实现接口时不是满足 is a 关系,而是具备这种行为或者是能力。接口允许多实现,一个类可以具备多个能力,同时实现多个父接口。若子类是普通类并想实现多个父接口,子类需要覆写所有的抽象方法。
//游泳接口,表示具备游泳的能力
public interface ISwim {
public abstract void swim();
}
//跑的接口 ,表示具备跑的能力/行为
public interface IRun {
public abstract void run();
}
//飞的接口,表示具备飞的能力/行为
public interface IFly {
public abstract void fly();
}
public class Rabbit implements IRun{
@Override
public void run() {
System.out.println("兔子在跑");
}
}
public class Dog implements IRun,ISwim{
@Override
public void run() {
System.out.println("狗在跑");
}
@Override
public void swim() {
System.out.println("狗在游泳");
}
}
public class Duck implements IRun,ISwim,IFly{
@Override
public void fly() {
System.out.println("鸭子在飞");
}
@Override
public void run() {
System.out.println("鸭子在跑");
}
@Override
public void swim() {
System.out.println("鸭子在游泳");
}
}
public class Test {
public static void main(String[] args) {
// 接口也不能直接实例化对象,需要向上转型
IRun run = new Rabbit();
IRun run1 = new Dog();
IRun run2 = new Duck();
run.run();
run1.run();
run2.run();
ISwim swim = new Dog();
ISwim swim1 = new Duck();
swim.swim();
swim1.swim();
IFly fly = new Duck();
fly.fly();
}
}
(2)接口表示一种规范或者标准。“USB接口”
interface USB {
//插入方法
public abstract void plugIn();
//工作方法
public abstract void work();
}
class Mouse implements USB{
@Override
public void plugIn() {
System.out.println("安装鼠标驱动~~");
}
@Override
public void work() {
System.out.println("鼠标开始正常工作!");
}
}
class KeyBoard implements USB {
@Override
public void plugIn() {
System.out.println("安装键盘驱动中~~");
}
@Override
public void work() {
System.out.println("键盘开始正常工作 ~ ");
}
}
public class Computer {
public static void main(String[] args) {
Computer computer = new Computer();
Mouse mouse = new Mouse();
KeyBoard keyBoard = new KeyBoard();
computer.fun(mouse);
computer.fun(keyBoard);
}
public void fun(USB usb){
usb.plugIn();
usb.work();
}
}
2、接口中只有全局常量和抽象方法,因此接口中public abstract ->抽象方法;static final->常量;全都可以省略。(只能在接口中省略,抽象类不行!!)
3、接口和类之间的关系
接口和接口之间也存在继承关系(可以是多继承),但接口坚决不能继承一个类
interface IA{
void testA();
}
interface IB{
void testB();
}
//IC接口可以同时继承多个父接口,并继承了所有的抽象方法
interface IC extends IA,IB{
}
//子类(普通子类)在实现IC接口时,必须覆写所有的抽象方法
class IcTest implements IC{
@Override
public void testA() {
}
@Override
public void testB() {
}
}
若一个类既需要继承一个类,同时实现多个接口时,先使用extends继承一个类,而后使用implements实现多个接口
interface IA{
void testA();
}
interface IB{
void testB();
}
abstract class Person{
}
class China extends Person implements IA,IB{
@Override
public void testA() {
}
@Override
public void testB() {
}
}
4、接口的命名规范
(1)为了区分接口和类,命名接口使用 I 开头,IRun , ISwim
(2)子类实现一个接口时,命名以相应的接口开头,以impl 结尾。如果子类实现多个父接口,不需要此规范来命名
如:若是 IRun的子类,RunImpl
三、Object类
全名称:包名.类名(java.lang.Object)
1、Object 类是Java中所有类的默认父类,无需使用 extends 来定义。只要是 class 声明的类都有一个 父类,即 Object 类。
因为Object 类是所有类的父类,使用 Object 引用来接收所有的类型,参数最高统一化。Java中所有类型都可以发生向上转型变成 Object 类。
void fun(Object obj){
// obj 可以接收任意类型的对象
}
public static void main(String[] args) {
//Object类是所有类(JDK本身的类,自定义的类)的父类
Object obj1 = "123";
Object obj2 = new Person();
Object obj3 = new String();
}
2、Object类中所有方法子类全都继承下来了,如toString方法
System.out.println(任意的引用数据类型)都可以打印->默认都调用toString()方法,Object类存在toString()。
当子类覆写了object类的toString方法,obj2 引用就调用覆写后的方法
3、Java中引用数据类型之间的 相等比较使用equals方法!不能使用“==”,“==”比较的是地址
public class ObjectTest {
public static void main(String[] args) {
Student student1 = new Student("小羊", 18);
Student student2 = new Student("小朱", 16);
Student student3 = new Student("小羊", 18);
//"=="比较的是数值,对于引用数据类型来讲,保存的内容就是一个地址,
// “==”在这里比较的是地址
//false
// System.out.println(student1 == student2);
// //false
// System.out.println(student1 == student3);
// System.out.println(student1);
// System.out.println(student2);
// System.out.println(student3);
//若需要比较两个对象的属性值是否相同,就需要按照比较规则覆写equals方法
System.out.println(student1.equals(student2));
System.out.println(student1.equals(student3));
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
//1.若当前对象就是obj
if (this == obj) {
return true;
}
//2.此时当前对象和obj指向的对象确实不是一个地址,
// 就需要向下转型,比较Student类中特有的属性值是否相同,
// 向下转型先要规避风险 instance of
if (obj instanceof Student) {
Student student = (Student) obj;
//this.name.equals(student.name)比较的是当前对象和传入对象对name这一String类型的属性值的比较
//String类中equals方法已经覆写了,不需要我们再人为操作
return this.name.equals(student.name) && this.age == student.age;
}
return false;
}
}
4、Object类不仅是所有类(class)的父类,JDK对Object类做了扩展,即Object类可以接收所有引用数据类型的对象(接口、数组、类)。因此在Java中,若一个方法参数或者返回值是Object类型,说明该参数或者返回值可以是任意引用数据类型(数组、类、接口)
interface ITest{
void print();
}
class TestImpl implements ITest{
@Override
public void print() {
System.out.println("TestImpl类的print方法" );
}
}
public class ObjectTest {
public static void main(String[] args) {
//接口引用
ITest i1 = new TestImpl();
//整形数组引用
int[] data = new int[10];
//只要是引用数据类型,都可以使用Object来接收
Object obj1 = i1;
Object obj2 = data;
}
}
四、JDK内置的两大重要接口
接口优先原则,当一个场景中既可以使用抽象类也可以使用接口定义时,优先考虑使用接口(更灵活)
1、Comparable接口(java.lang.Comparable):
当一个类实现了Comparable接口,表示该类具备了可比较的能力。
public class ComparableTest {
public static void main(String[] args) {
Person[] people = new Person[]{
new Person("小明",22),
new Person("小李",20),
new Person("小王",18)
};
Arrays.sort(people);
System.out.println(Arrays.toString(people));
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
由于Person这个类型是自定义类型,对于编译器来说,不像int这样大小关系一目了然,到底哪个Person对象大,哪个Person对象小,编译器无从得知。要让Person这个类型具备可比较的能力,也就是让JDK知道Person对象“谁大谁小”,就需要让Person类 实现Comparable接口,覆写抽象方法Compare to()。
public class ComparableTest {
public static void main(String[] args) {
Person[] people = new Person[]{
new Person("小王",18),
new Person("小明",22),
new Person("小李",20),
};
Arrays.sort(people);
System.out.println(Arrays.toString(people));
}
}
class Person implements Comparable{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o) {
if(this == o){
return 0;
}
if (o instanceof Person){
Person per = (Person) o;
//根据年龄比较大小,从小到大顺序排列
// return this.age - per.age;
//从大到小逆序排列
return per.age - this.age;
}
//报错,抛出异常
throw new IllegalArgumentException("不是Person类型,无法比较");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2、Cloneable接口(java.lang.Cloneable):
要让一个类具备可复制的能力,实现Cloneable接口,覆写clone方法
类似Cloneable接口,这种接口称之为“标记”接口,这个接口本身内部没有任何抽象方法,只有打上这个标记的子类才具备克隆的能力。
public class CloneableTest {
public static void main(String[] args) {
Animal animal1 = new Animal("多利");
Animal animal2 = animal1.clone();
System.out.println(animal1.equals(animal2));
System.out.println(animal1 == animal2);
}
}
class Animal implements Cloneable{
String name;
public Animal(String name) {
this.name = name;
}
public Animal clone(){
Animal newAnimal = null;
//调用的是Object类提供的clone方法
try {
//强转
newAnimal =(Animal) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return newAnimal;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if (obj instanceof Animal){
Animal animal = (Animal) obj;
return this.name.equals(animal.name);
}
throw new IllegalArgumentException("不是Animal类,无法比较");
}
}
2.2深浅拷贝
(1)浅拷贝:克隆的对象和原对象确实是两个独立的对象,对象的内部若包含了其他类的引用,克隆后的对象b2包含的其他类的引用并没有产生新的对象。 b1.a 和b2.a指向相同的对象
(2)深拷贝:b2克隆对象内部包含的其他类的引用也产生了新的对象,b1.a 和b2.a指向不同的对象。Java中深拷贝的实现方式:1.递归使用clone方法;2序列化(json字符串)
2.3 调用clone方法产生的对象,不会调用构造方法。
在Java中产生一个新对象有两种方式:
(1)最普通最常见的是通过构造方法产生对象 new 类();->当有new关键字,就在对上开辟该类相应属性的空间,给属性赋默认值。
(2)通过clone()产生对象,调用clone时,JVM会开辟与原对象内存大小完全相同的新空间,并将对象中属性的值从原对象中复制一份。(不推荐使用这种方式产生对象)
还没有评论,来说两句吧...