一、抽象类
1.1 什么是抽象类
有 abstract 修饰的类,就是抽象类。
语法格式:
【其他修饰符】 abstract class 抽象类名{
}
1.2 为什么要用抽象类
首先,父类是代表众多子类的共同的特征。
其次,当类的层次结构多了之后,越往上的类就变得越抽象,越往下的子类就变的越具体。
当定义上层的父类时,某些方法无法给出具体的实现了,只能声明其方法头,方法体写不了,这样的方法就需要被定义为抽象方法。
Java 中规定,凡是类中有抽象方法,这个类必须是抽象类。
方法 = 方法头 + 方法体
【修饰符】 返回值类型 方法名(【形参列表】) {
方法体
}
【修饰符】 返回值类型 方法名(【形参列表】) ==> 方法头
抽象方法的语法格式:
【其他修饰符】 abstract 返回值类型 抽象方法名(【形参列表】) ; //直接;结束,没有{方法体}
两句话:
- 凡是类中有抽象方法,这个类必须是抽象类
- 如果一个类中没有抽象方法,这个类也可以是抽象类
1.3 抽象类与普通类有什么不同
- 抽象类不能直接 new 对象
- 子类继承抽象类后,必须重写抽象父类的所有抽象方法。否则,子类也得是抽象类。实现(或重写)抽象方法的快捷键是 Ctrl + I。(这里用 Ctrl + O 也可以,只是 Ctrl + O 包含父类抽象和非抽象方法)
问:抽象类有构造器吗?有
抽象类的构造器是给儿子用的。因为子类必须调用父类的构造器,默认调用父类的无参构造。
package com.atguigu.chouxiang;
/*
Graphic它也有父类,默认是Object。父类可以不是抽象的。
*/
public abstract class Graphic {//图形类
private String name;
public Graphic() {
}
public Graphic(String name) {
this.name = name;
}
//求面积
public abstract double area();
}
package com.atguigu.chouxiang;
public class Rectangle extends Graphic{//矩形
private double length;//长
private double width;//宽
public Rectangle() {
super();
}
public Rectangle(double length, double width) {
super();
this.length = length;
this.width = width;
}
public Rectangle(String name,double length, double width) {
super(name);
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public String toString() {
return "长:" + length +
",宽:" + width +
",面积:" + area();
}
@Override
public double area() {
return length * width;
}
}
package com.atguigu.chouxiang;
public class TestGraphic {
public static void main(String[] args) {
// Graphic g = new Graphic();//错误
Rectangle g = new Rectangle(8,6);
double area = g.area();
System.out.println("面积:" + area);
System.out.println(g);
}
}
二、接口
2.1 为什么要用接口?
接口是对类进行补充的一种类型。它接口解决:
- 解决类的单继承的限制问题。
- 它可以给多个不是同一类事物的类,提供相同方法的抽取,表示它们遵循同一个标准。例如:Plane、Bird、Kite 有相同的操作方法 fly,它们遵循同一标准。
2.2 如何声明接口(重点)
语法格式:
【修饰符】 interface 接口名{
}
2.3 类如何实现接口(重点)
语法格式:
【修饰符】 class 类名 【extends 父类】 implements 接口们{ //接口们代表可以是多个接口,接口1,接口2,接口3
}
//这个类可以被称为接口的子类,也可以被称为实现类(后者更多)
- 类与接口之间可以多实现
- 当类实现接口时,如果这个类本身不是抽象类,那么这个类就必须实现接口的所有抽象方法
- 如果子类同时要继承父类,又要实现父接口,要遵循先继承父类再实现接口
Java 的类与类之间是单继承,类与接口之间是多实现。
比喻:子类与父类比喻为亲儿子和亲生父亲,亲生父亲只有 1 个;
子类与父接口比喻为干儿子和干爹,干爹可以有多个。
2.4 接口与接口之间可以是什么关系?(了解)
语法格式:
【修饰符】 interface 子接口 extends 父接口们{
}
- 接口与接口之间是多继承
2.5 示例代码
2.5.1 示例代码 1
package com.atguigu.jiekou;
//声明为abstract,可以避免创建Animal的对象
public abstract class Animal {//动物
}
package com.atguigu.jiekou;
//接口默认就是抽象的,不需要加abstract
public interface Flyable {
//在接口中,抽象方法默认都是public abstract
//可以省略public abstract
// public abstract void fly();
void fly();
}
package com.atguigu.jiekou;
public interface MyRunnable {
void run();
}
package com.atguigu.jiekou;
public class Bird extends Animal implements Flyable,MyRunnable {//鸟
@Override
public void fly(){
System.out.println("我要展翅高飞!");
}
@Override
public void run() {
System.out.println("我会蹦蹦跳跳跑");
}
}
package com.atguigu.jiekou;
public class Plane implements Flyable{//飞机
@Override
public void fly(){
System.out.println("我要飞入云霄!");
}
}
package com.atguigu.jiekou;
public class Kite implements Flyable{//风筝
@Override
public void fly(){
System.out.println("我飞的再高,线也在你手里!");
}
}
package com.atguigu.jiekou;
public class TestFlyableAndMyRunnable {
public static void main(String[] args) {
Bird b = new Bird();
b.fly();
b.run();
Plane p = new Plane();
p.fly();
Kite k = new Kite();
k.fly();
// Flyable f = new Flyable();//错误,不能直接创建接口的对象,只能创建接口的实现类的对象
}
}
2.5.2 示例代码 2
package com.atguigu.jiekou;
public interface A {
void a();
}
package com.atguigu.jiekou;
public interface B {
void b();
}
package com.atguigu.jiekou;
//接口与接口之间实现多实现
public interface C extends A,B{
void c();
}
package com.atguigu.jiekou;
public class D implements C{
@Override
public void c() {
}
@Override
public void a() {
}
@Override
public void b() {
}
}
2.6 接口的成员
2.6.1 JDK8 之前(重点)
接口的成员只能是:
- 公共的静态的 final 的常量:public static final,可以省略
- 公共的抽象方法:public abstract,可以省略,它没有方法体。非抽象子类必须重写。
接口中没有构造器、普通的成员变量、普通的成员方法。
2.6.2 JDK8(含)之后(能看懂即可)
在之前的基础上,增加了如下两类成员:
公共的静态方法:public static ,其中 public 可以省略,static 不可以省略,它有方法体。子类不能重写。
- 解释:因为静态方法本身与类或接口的对象无关,按照原来的版本,接口中不允许出现静态方法,那么如果有静态方法的需求,就需要单独找一个类装这个静态方法,就增加了维护的成本,因为你需要同时维护接口 和 这个类 。现在接口中允许定义静态方法,代码更紧凑了,遵循了高内聚的开发原则。
公共的默认方法:public default,其中 public 可以省略,default 不可以省略,它也有方法体。子类可以重写,可以不重写。
- 解释:(1)某个接口的抽象方法,在多个子类中实现的方法体是基本一致的,那么这个方法实现就可以抽取到父接口中,这样避免冗余的重复代码。(2)早期的很多接口,里面只有抽象方法。当版本更新之后,这些接口有可能也要更新(增加新成员),如果按照之前的原则,接口更新只能增加抽象方法,这会导致之前这个接口的所有实现类都报错,都必须重新实现这个新的抽象方法,那么维护的成本就非常高。所以把这个新的抽象方法定为默认方法,旧是实现类可以选择重新实现,也可以选择不实现,比较灵活。
父类的方法不是所有方法都重写的。
(1)final 不能重写
(2)private 不能重写
(3)static 不能重写
2.6.3 JDK9(含)之后(了解)
接口又增加了一个成员:
- 私有的方法:private ,其中 private 不能省略,私有方法可以是静态的,可以是非静态的,但是私有方法不能是 abstract,也不能是 default。
- 解释:按照 JDK8 版本的升级,接口中的静态方法和默认方法有方法体了,多个方法之间就可能存在相同的方法实现,这些是冗余的重复代码,需要抽取出来一个方法,这个方法又只是接口的内部使用的,所以应该定义为 private。
2.6.4 接口的成员演示
package com.atguigu.jiekou;
public interface Drivable {//可以开的
//public static final
int MAX_SPEED = 500;//公共的静态的常量
void drive();//公共的抽象方法,省略了public abstract
static void show(){//公共的静态方法,省略了public
display();
System.out.println("我开.....心起来");
}
default void on(){//公共的默认方法,省略了public
display();
System.out.println("启动....");
}
private static void display(){//私有静态方法
System.out.println("我是一个接口");
}
}
package com.atguigu.jiekou;
public class Car implements Drivable{
@Override
public void drive() {//必选
System.out.println("我是su7,我跑起来");
}
@Override
public void on() {//可选
System.out.println("start....");
}
}
package com.atguigu.jiekou;
public class TestDrivable {
public static void main(String[] args) {
//调用接口的公共的静态的常量
System.out.println("时速的最大值:" + Drivable.MAX_SPEED);
//调用接口的抽象方法
Car car = new Car();
car.drive();
//调用接口的静态方法
Drivable.show();
//调用接口的默认方法
car.on();
//调用接口的私有方法
// Drivable.display();//错误。private不可以跨类使用
}
}
2.6.5 冲突问题(了解)
1、父类的成员变量与父接口的公共的静态常量重名了
package com.atguigu.problem;
public class Father {
int MAX_VALUE = 1;
}
package com.atguigu.problem;
public interface DryFather {//dry 干燥
int MAX_VALUE = 2;//它隐藏了 public static final
}
package com.atguigu.problem;
public class Son extends Father implements DryFather{
public void show(){
System.out.println("父类的MAX_VALUE:" + super.MAX_VALUE);
System.out.println("父接口的MAX_VALUE:" + DryFather.MAX_VALUE);
}
}
package com.atguigu.problem;
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.show();
}
}
2、父类的成员方法与父接口的默认方法冲突了
遵循:默认亲爹优先原则
当然子类也可以主动选择,进行重写。
package com.atguigu.problem;
public class Fu {
public void show(){
System.out.println("我是父类的方法(1)");
}
}
package com.atguigu.problem;
public interface DryFu {
default void show(){
System.out.println("我是父接口的方法(2)");
}
}
package com.atguigu.problem;
public class Sub extends Fu implements DryFu{
@Override
public void show() {
// super.show();//父类的
// DryFu.super.show();//父接口的
System.out.println("我自己来");
}
}
package com.atguigu.problem;
public class TestSub {
public static void main(String[] args) {
Sub sub = new Sub();
sub.show();
}
}
3、当两个父接口的默认方法冲突了
遵循:必须做出选择的原则,即重写
package com.atguigu.problem;
public interface DryDieOne {
public default void show(){
System.out.println("我是第1个干爹的方法");
}
}
package com.atguigu.problem;
public interface DryDieTwo {
public default void show(){
System.out.println("我是第2个干爹的方法");
}
}
package com.atguigu.problem;
public class ErZi implements DryDieOne,DryDieTwo{
@Override
public void show() {
// DryDieOne.super.show();
// DryDieTwo.super.show();
System.out.println("我学习");
}
}
package com.atguigu.problem;
public class TestErZi {
public static void main(String[] args) {
ErZi z = new ErZi();
z.show();
}
}
2.7 抽象类与接口的区别
相同点:
- 它们都不能直接 new 对象
- 它们都可能包含抽象方法
抽象类与接口是面向对象编程中两个重要的概念,尤其在 Java 等语言中使用广泛。它们都用于实现抽象和多态,但有以下主要区别:
区别点 | 抽象类(Abstract Class) | 接口(Interface) |
---|---|---|
方法实现 | 可以包含抽象方法和具体方法的实现 | 在 Java 8 前只能包含抽象方法;Java 8 及之后可以包含默认方法 (default ) 和静态方法 |
构造器 | 有构造器,可以被子类调用 | 没有构造器 |
成员变量 | 可以定义普通成员变量 | 只能是 public static final 的常量 |
继承关系 | 子类通过 extends 继承抽象类,且只能单继承 | 类通过 implements 实现接口,一个类可以实现多个接口 |
抽象访问修饰符 | 方法可以使用public 、 protected 、缺省修饰符 | 方法默认是 public abstract ,不能使用其他访问修饰符 |
设计目的 | 表示“是什么(is -a)”的关系,强调的是类的本质 | 表示“具有什么能力(has-a)”的关系,强调行为规范 |
总结来说:
- 如果你需要共享代码或维护状态(这里的状态是指各种属性值),优先选择抽象类;
- 如果你需要定义行为契约,并允许多个
不相关
的类实现该契约,优先选择接口。
关键点:
(1)单继承和多实现
(2)成员的情况不同
(3)子类与它的关系是什么?is -a 还是 has-a
三、关键字
回忆咱们已经学过哪些关键字?
基本数据类型:byte,short,int,long,float,double,char,boolean(8种)
空类型:void
引用数据类型:interface(定义接口)、class(定义类)
流程控制语句结构:if、else、switch、case、default、for、do、while、continue、return、break、
对象:new、this、super
关系:extends、implements
包:import、package
修饰符:abstract、static、public、private、final、protected
上下文关键字:yield
特殊值:
true, false, null
保留字:
goto、const、_
3.1 native(了解)
native:本地的,原生的
在 Java 中用于修饰方法,这个方法在 Java 层面没有方法体,它的方法体实现在 C/C++底层代码中。
对于 native 的方法,能重写的正常重写,该怎么调用怎么调用,除了方法体代码,与 Java 方法没有什么区别。
3.2 Object 类的方法
1、hashCode:
public native int hashCode();用于产生一个哈希值,它类似于是对象的身份证号码。
2、equals 方法
public boolean equals(Object obj):用于比较两个对象是否“相等”。
3、hashCode 和 equals 方法一起重写
package com.atguigu.keyword;
import java.util.Objects;
public class Student {
//实例变量,私有化
private String name;
private int score;
//无参构造,有参构造
public Student() {
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
//get/set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//重写toString等
@Override
public String toString() {
return "Student{" +
"score=" + score +
", name='" + name + '\'' +
'}';
}
//重写hashCode方法的快捷键 (1)Ctrl+O (2)Alt+Insert(推荐)
@Override
public boolean equals(Object o) {//形参是Object类型,但是实际上,实参应该是Student等对象
/*
测试类中 s1.equals(s2)
this是当前对象,表示调用equals方法的对象。针对上面这句代码,this是s1
o是()中接收的实参对象。。针对上面这句代码,o是s2
*/
if (this == o) return true; //如果this==o,等价于 s1==s2,它们的地址相同
if (o == null || getClass() != o.getClass()) return false;
/*
如果 o == null ,就直接返回false。因为如果能进入到这个方法中,说明s1一定不是null,如果s1是null,就发生空指针异常NullPointerException。
既然s1不为null,o为null,肯定不相等。
如果getClass() != o.getClass() ,就直接返回false。
getClass() 它其实是 this.getClass() 表示的是返回this对象的类型,即s1对象的类型
o.getClass() 它就是返回o对象的类型,即s2对象的类型
如果两个对象的类型不同,那么就是false
*/
Student student = (Student) o; //强制转换
return score == student.score && Objects.equals(name, student.name);
// this.score == student.score 且 Objects.equals(this.name, student.name)(比较的是名字)
}
@Override
public int hashCode() {
return Objects.hash(name, score);
}
}
package com.atguigu.keyword;
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student("张三",99);
Student s2 = new Student("张三",99);
System.out.println(s1 == s2);//false
//因为Student是类,s1,s2是引用数据类型的变量
//s1,s2中就存储的是对象的首地址
//两次new的对象地址值肯定不一样的
System.out.println(s1.equals(s2));//true
//如果Student类没有重写equals方法,那么s1.equals(s2)等价于 s1==s2
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
/*
Java中规定:
(1)两个相等的对象,它们的hashCode(哈希值)一定相同
A:==相等(绝对相同,地址都一样,物理上就是同一个对象
B:equals相等(相对相同,里面的属性值相同)
(2)如果两个对象的hashCode(哈希值)不同,那么它们一定不相等
(3)如果两个对象的hashCode(哈希值)相同,那么它们可能相等,可能不同
可能存在两个不同的人,身份证号码一样
因为hashCode也是根据某个公式算出来一个int值
y=f(x),两个不同的x,可能得到相同的y
*/
String str1 = "Aa";
String str2 = "BB";
System.out.println(str1.equals(str2));//false
System.out.println(str1.hashCode());//2112
System.out.println(str2.hashCode());//2112
/*
到目前为止哈希值对于我们来说,还没有什么意义。
等到咱们哈希结构的集合等容器时,才有意义,例如:HashSet、HashMap
*/
}
}