该学习笔记是根据阿玮老师《黑马程序员Java零基础视频教程》总结出来的,非常感谢阿玮老师。
一. 抽象类
- 抽象方法是指将共性的行为(成员方法)抽取到父类后,由于每一个子类执行的内容是不一样的,所以在父类中不能确定具体的方法体,此时该方法就可以定义为抽象方法。抽象类是指如果一个类中存在抽象方法,那么该类就必须声明为抽象类(别管存在几个抽象方法,只要存在就行)。抽象方法的定义就是加上
abstract关键字
,然后去掉大括号,直到分号结束。即public abstract 返回值类型 方法名(参数列表);
,抽象类的定义格式:public abstract class 类名{}
。 - 抽象类和抽象方法的注意事项:
(1)抽象类不能实例化对象(也就是说抽象类不能创建对象);
(2)抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类;
(3)抽象类可以有构造方法,当创建子类对象时,给属性(成员变量)进行赋值;
(4)抽象类的子类要么重写抽象类中的所有抽象方法(推荐这样整),要么这个子类就是抽象类。
(5)需要注意的是,在定义抽象方法时,不能使用private
、final
和static
修饰符。这是因为,private
修饰的方法不能被继承,final
修饰的方法不能被重写,static
修饰的方法属于类,不属于对象,无法被子类重写。
(6)抽象类和抽象方法的意义:抽象方法会强制要求子类必须按照这种格式进行重写(也就是说一旦你把这个方法定义为抽象方法,那么你在子类中必须必须必须按照父类的方法格式进行重写,不重写都不行,强制重写。如果这个方法不是抽象方法,那么你在子类中重写或者不重写都OK啦)。
/*
要求: 1.青蛙(Frog) -- 属性:名字,年龄; 行为:吃虫子,喝水.
2.狗(Dog) -- 属性:名字,年龄; 行为:吃骨头,喝水.
3.山羊(Sheep) -- 属性:名字,年龄; 行为:吃草,喝水.
*/
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("大黄", 5);
dog.eat();
dog.drink();
System.out.println(dog.getName()+", "+dog.getAge());
}
}
class Frog extends Animal{
public Frog(){}
public Frog(String name, int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃虫子");
}
}
class Dog extends Animal{
public Dog(){}
public Dog(String name, int age){
super(name,age); //通过super(name,age)访问父类Animal的带参构造方法
}
@Override
public void eat(){
System.out.println("吃骨头");
}
}
class Sheep extends Animal{
public Sheep(){}
public Sheep(String name, int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("吃草");
}
}
abstract class Animal{
// 父类Animal
private String name;
private int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public abstract void eat();
public void drink(){
System.out.println("动物在喝水");
}
}
/*
输出:
吃骨头
动物在喝水
大黄, 5
*/
二. 接口
- 接口相当于一种规则,是行为的抽象,想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。接口的定义:接口使用关键字interface(接口、界面)来定义,定义格式:
public interface 接口名字 {}
。接口的使用:
(1)接口不能实例化对象(也就是说接口不能创建对象);
(2)接口和类之间是实现关系,通过关键字implements(实现)表示,实现的方式:public class 类名 implements 接口名字 { }
;
(3)接口的子类(实现类)要么重写接口中的所有抽象方法(推荐使用),要么是抽象类。 - 接口使用的注意事项:
(1)接口和类的实现关系,可以单实现,也可以多实现,即public class 类名 implements 接口1, 接口2,...{}
;
(2)实现类还可以在继承一个类的同时实现多个接口,即public class 类名 extends 父类 implements 接口名1, 接口名2,...{}
。
(3)接口中的成员:成员变量、成员方法、构造方法(不存在)。其中,接口中的成员变量只能是常量,默认修饰符为public static final
(这里的默认是指:就算你没有写public static final,java也会帮你加上这个修饰符);接口中没有构造方法;在JDK1.7以前接口中只能定义抽象方法,默认修饰符为public abstract
。JDK1.8接口中可以定义有方法体的方法,JDK1.9接口中可以定义私有方法。当一个方法的形参是接口时,可以传递接口所有实现类的对象,这种方式称为接口多态。 - 接口、类之间的关系
- 类和类的关系:存在继承关系,且只能单继承,不能多继承,但是可以多层继承;
- 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口;
- 接口和接口的关系:继承关系,可以单继承,也可以多继承。(如果实现类实现了最下面的子接口,那么就需要重写所有的抽象方法)。
- JDK1.8开始接口中新增的方法
- JDK1.7以前:接口中只能定义抽象方法。
- JDK1.8的新特性:接口中可以定义有方法体的方法(默认方法、静态方法)。
(1)允许在接口中定义默认方法,需要使用关键字default修饰,默认方法的作用是解决接口升级的问题(接口升级是指在接口中添加新的方法,具体添加几个新方法根据实际而定)。
(2)接口中的默认方法定义格式为:public default 返回值类型 方法名(参数列表){}
,例如public default void show(){}
。默认方法不是抽象方法,所以不被强制重写,但是如果想被强制重写,重写的时候去掉default关键字即可。默认方法中public可以省略,default不能省略;如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。
(3) 允许在接口中定义静态方法,需要用static修饰。接口中静态方法的定义格式:public static 返回值类型 方法名(参数列表){}
,例如public static void show(){}
。静态方法只能通过接口名去调用,不能通过实现类名或者对象名去调用;public可以省略,static不能省略;静态方法不需要重写。 - JDK1.9的新特性:接口中可以定义私有方法。接口中私有方法的定义格式,
格式1: private 返回值类型 方法名(参数列表){}
,例子:private void show(){}
;格式2: private static 返回值类型 方法名(参数列表){}
,例子:private static void method(){}
。格式1是普通的私有方法,给默认(default)方法服务的;格式2是静态的私有方法,给静态(static)方法服务的。
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("大黄",6);
dog.eat();
dog.swim();
}
}
interface Swim {
public abstract void swim(); //public abstract 可以省略
}
class Dog extends Animal implements Swim{
public Dog(){}
public Dog(String name, int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("狗吃骨头");
}
@Override
public void swim(){
System.out.println("狗会游泳");
}
}
abstract class Animal{
// 父类Animal
private String name;
private int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public abstract void eat();
}
/*
输出:
狗吃骨头
狗会游泳
*/
三. 接口与抽象类的区别
- 两者的相同点
- 都不能实例化对象;
- 接口的实现类或抽象类的子类都只有实现了接口或继承抽象类中的方法后才能实例化。
- 不同点
- 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
- 抽象类可以有构造方法,接口中不能有构造方法;
- 抽象类中可以包含非抽象的普通方法,接口中的可以有非抽象方法,比如deaflut方法;
- 接口成员变量默认为public static final,必须赋初值,不能被修改;接口中所有的抽象方法都是用public abstract来修饰。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值。
四. 内部类
- 类的五大成员:成员变量(属性)、成员方法(行为)、构造方法、代码块、内部类。而内部类就是在一个类的里面再定义一个类,例如在A类的内部定义B类,B类就被称为内部类。内部类表示的事物是外部类的一部分,内部类单独出现没有任何意义。内部类可以直接访问外部类的成员,包括私有的;外部类要访问内部类的成员,必须创建对象。
- 内部类分为:成员内部类、静态内部类、局部内部类、匿名内部类。其中成员内部类、静态内部类、局部内部类我们几乎不会自己去编写,可能看源代码的时候会遇到,所以了解就行。
- 成员内部类
成员内部类是写在成员位置的,属于外部类的成员。成员内部类可以被一些修饰符所修饰,例如被private、默认、protected、public等等修饰。在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。创建内部类对象:
方式1:在外部类中编写方法,对外提供内部类的对象;
方式2:直接创建:外部类名.内部类名 对象名 = 外部类对象.内部类对象,例子:Car.Engine car = new Car().new Engine();
。
public class Car { //外部类
String carName;
private int carAge;
String carColor;
class Engine{ //此处的Engine就是成员内部类,它和上面的成员变量carName、carAge、carColor是同一级别的
String engineName;
int engineAge;
public void show(){
System.out.println(engineAge);
System.out.println(carName);
System.out.println(carAge);
}
}
}
- 静态内部类
静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象。创建静态内部类对象的格式:外部类名.内部类名 对象名 = new 外部类名().内部类名();
。调用非静态方法的格式:先创建对象,用对象调用。调用静态方法的格式:外部类名.内部类名.方法名();
。静态内部类也是成员内部类的一种,只不过静态内部类是一种特殊的成员内部类;
public class Car { //外部类
String carName;
private int carAge;
String carColor;
static class Engine{ //此处的Engine就是静态内部类,它和上面的成员变量carName、carAge、carColor是同一级别的
String engineName;
int engineAge;
}
}
- 局部内部类
将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。在外界是无法直接使用局部内部类的,需要在方法内部创建对象并使用。局部内部类可以直接访问外部类的成员,也可以访问方法内的局部变量。局部内部类代码如下:
public class Car { //外部类
String carName;
private int carAge;
String carColor;
public void show(){
int a = 10;
class Engine{ // 此时Engine为局部内部类
}
}
}
- 匿名内部类
匿名内部类本质上就是隐藏了名字(没有名字)的内部类,可以写在成员位置,也可以写在局部位置。匿名内部类的格式为:父类/接口 对象 = new 父类/接口(){重写方法};
,例子:new Inter(){public void show(){}};
。
(1)上面的例子包含了继承\实现、方法重写、创建对象,整体就是一个父类(抽象类)的子类对象或者接口的实现类对象。
(2)匿名内部类的使用场景:当方法的参数是接口或者类时,以接口为例,可以传递这个接口的实现类对象,如果实现类只使用一次,就可以用匿名内部类简化代码。看代码理解:
public class Test {
public static void main(String[] args) {
/*
下面是一个匿名内部类的代码.格式为:new 类名或者接口名(){重写方法}; 整体我们可以理解为Swim接口的实现类对象。
此处Swim是一个接口,后面的{@Override public void swim(){System.out.println("重写了游泳的方法");}}是Swim接口的实现类(子类),
也就是说我们通过new关键字创建了一个Swim接口实现类对象.
*/
/*
new Swim(){
@Override
public void swim(){
System.out.println("重写了游泳的方法");
}
}
上面整体就等价于:接口Swim的实现类对象
*/
Swim sm = new Swim(){
@Override
public void swim(){
System.out.println("重写了游泳的方法");
}
};
// 上面的是一个对象
System.out.println(sm);
}
}
interface Swim{
public abstract void swim();
}
/*
输出
com.itheima.demo6.Test$1@3d075dc0
*/