抽象类

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,然后再用abstract来修饰该类,这个类就是抽象类。

  1. abstract public class Person {
  2. public abstract void say(); //加分号
  3. }

所谓抽象方法就是没有实现的方法(没有方法体),当一个类中存在抽象方法时,需要将该类声明为abstract方法。一般来说,抽象类会被继承,由其子类来实现抽象方法。

简介

  1. 用 abstract 关键字来修饰一个类时,这个类就叫抽象类。
    1. 访问修饰符 abstract 类名{}
  2. 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法。
    1. 访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体
  3. 抽象类的价值更多作用是在于设计,设计者设计好后,让子类继承并实现抽象类,在框架和设计模式使用较多(常考)。

    注意事项

  4. 抽象类不能被实例化。
    image.png
    2. 抽象类可以没有abstract方法,但只要类是abstract,那么它的方法即使不写abstract,也是抽象方法
    3. 一旦类包含了abstract方法,则这个类必须声明为abstract。
    4. abstract 只能修饰类和方法,不能修饰属性和其它的。
    5. 抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法,构造器,静态属性等等。
    6. 抽象方法不能有主体,即不能有大括号。
    1. public abstract void say(){}; //报错,不能有大括号
    7. 如果一个类继承了抽象类,那么它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
    image.png
    8. 抽象方法不能使用 private, final 和 static来修饰,因为这些关键字都是和重写相违背的。private不能重写,static与重写无关。
    image.png
    关于static可以看这个博客:关于abstract为什么不能和static连用_sinat_36695865的博客-CSDN博客

    模板设计模式

    ```java abstract public class Template { //抽象类-模板设计模式 public abstract void job(); //抽象方法
    public void calculateTime(){ //主体方法
    1. long start = System.currentTimeMillis();
    2. job(); //调用抽象方法
    3. long end = System.currentTimeMillis();
    4. System.out.println("执行时间为:"+(end-start));
    } }

public class AA extends Template{
@Override
public void job() { //重写抽象方法
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
} }

public class Test {
public static void main(String[] args) {
AA a = new AA(); a.calculateTime(); //直接调用方法 } }

  1. 这个设计模式的好处就是 —— **把需要重写的方法做成 抽象方法写在抽象父类中,子类只需要把不同的代码重写即可,相同部分的方法写在抽象类中**, 子类不需要动,在测试类中实例化对象直接调用即可。
  2. <a name="P9GmP"></a>
  3. ## 接口
  4. <a name="lvhrG"></a>
  5. ### 基本介绍
  6. **接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来,类似于抽象类。**
  7. ```java
  8. interface 接口名{
  9. //属性
  10. //方法(1.抽象方法 2.默认实现方法 3.静态方法)
  11. }
  12. class 类名 implements 接口名{
  13. //自己的属性
  14. //自己的方法
  15. //必须实现的接口抽象方法
  16. }

注:1. 在Jdk7.0 之前,接口里的所有方法都没有方法体,即都是抽象方法(abstract可省略)2. Jdk8.0 后,接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。

  1. public interface Interface01 {
  2. public int n1 = 10;
  3. public void h1(); //接口中可省略abstract
  4. default public void say(){
  5. //默认方法,要加上关键字default
  6. System.out.println("调用say方法");
  7. }
  8. public static void hi(){ //静态方法
  9. System.out.println("打招呼");
  10. }
  11. }

注意事项

1. 接口不能被实例化。

  1. Interface01 a = new Interface01(); //报错

2. 接口中所有的方法都是public abstract方法,接口中抽象方法可以不用abstract修饰

  1. void h1(); //实际上是 public abstract void h1();

如何验证:

  1. public class Teacher implements Interface01{
  2. @Override
  3. void h1() {} //报错,因为根据继承关系,重写方法时不能减小范围,间接证明为public
  4. }

3. 一个普通类实现接口,就必须将该接口的所有抽象方法实现,快捷键:点击要实现接口的名字,按 Alt + Enter ,点击 Implement methods,然后选中要实现的方法。
image.png
image.png
4. 抽象类实现接口,可以不用实现接口的抽象方法。
5. 一个类可以同时实现多个接口,中间用逗号隔开。

  1. class Person implements IB,IC {}

6. 接口中的属性,前面默认有 public static final 修饰符,必须初始化
7. 接口中属性的访问形式:接口名.属性名(因为是静态属性)

  1. System.out.println(Interface01.n1);

8. 一个接口不能继承其他的类,但是可以继承多个别的接口。

  1. public interface Interface01 extends Interface02,Interface03 {}

9. 接口的修饰符只能是 public 和 默认,这点和类的修饰符是一样的。

  1. private interface Interface01{} //报错,修饰符只能是public或默认

10. 用对象名可以调用接口中的属性(另外也可以用类名和接口名调用),但是IDEA没有提示。

  1. public class Test {
  2. public static void main(String[] args) {
  3. Teacher a = new Teacher();
  4. System.out.println(Teacher.n1);
  5. System.out.println(Interface01.n1);
  6. System.out.println(a.n1); //没有报错,证明可以用对象名调用
  7. }
  8. }

11. 有继承还有接口的类,如果接口和父类某个属性重名,调用时要用接口名和super指定。

  1. public class Teacher extends Person implements Interface01 {
  2. public void show(){
  3. System.out.println(Interface01.x);
  4. //输出接口的x
  5. System.out.println(super.x);
  6. //输出父类的x
  7. System.out.println(x);
  8. //报错,无法确定哪个x
  9. }
  10. }

与继承的比较

当子类继承了父类,就自动的拥有父类的功能(类似父子关系),如果子类需要扩展功能,就可以通过实现接口的方式扩展(类似师徒关系)比如说金丝猴继承猴子类,拥有了一些猴子的属性,如果金丝猴想要游泳和飞翔,那么就需要实现鱼和鸟的接口。可以理解为:实现接口是对Java单继承机制的一种补充。
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计好各种方法,让其它类去实现这些方法。继承是满足 is - a 关系,而接口只需要满足 like - a 关系即可,接口比继承更加灵活。并且接口在一定程度上实现代码解耦。

接口的多态性

1. 多态参数
Usb接口参数既可以接收手机对象,又可以接收电脑对象,接口引用可以指向实现了接口的类的对象。

  1. public interface Usb { //接口
  2. void work();
  3. }
  4. public class Phone implements Usb{
  5. @Override
  6. public void work() {
  7. System.out.println("Usb在手机上工作");
  8. }
  9. }
  10. public class Computer implements Usb{
  11. @Override
  12. public void work() {
  13. System.out.println("Usb在电脑上工作");
  14. }
  15. }
  16. public class Test {
  17. public static void main(String[] args) {
  18. Test a = new Test();
  19. Phone b = new Phone();
  20. Computer c = new Computer();
  21. a.show(b); //接口的多态,类似向上转型
  22. a.show(c);
  23. }
  24. public void show(Usb a){ //形参是一个接口
  25. a.work();
  26. }
  27. }

image.png
2. 多态数组

  1. public class Test {
  2. public static void main(String[] args) {
  3. Usb[] usbs = new Usb[2];
  4. usbs[0] = new Phone();
  5. usbs[1] = new Computer();
  6. for (int i = 0; i < 2; i++) {
  7. usbs[i].work(); //调用接口中的方法
  8. if(usbs[i] instanceof Phone){
  9. ((Phone) usbs[i]).call();
  10. //类似向下转型,call是Phone类的独特方法
  11. }
  12. }
  13. }
  14. }

由于能调用什么方法是根据编译类型来的,因此想调用call方法需要用到向下转型(类似)。
3. 多态传递现象

  1. interface IA{
  2. void say();
  3. }
  4. interface IB extends IA{
  5. //IB接口继承自IA
  6. void hi();
  7. }
  8. public class Person implements IB{
  9. IA a = new Person();
  10. //这里就用到了多态传递现象
  11. @Override
  12. public void say() {
  13. }
  14. @Override
  15. public void hi() {
  16. } //方法必须全部实现
  17. }

很明显,Person类并没有实现IA接口,但是却可以用IA来实例化对象。原因是Person实现了IB接口,而IB接口继承自IA,Person类把IB和IA接口的方法全部实现,相当于实现了IA。
简而言之,类需要把implements接口的方法,以及接口父类的方法全部实现,然后可以用接口以及接口的子类实例化对象。

内部类(难点、重点)

基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类,嵌套内部类的类称为外部类,是我们类的第五大成员(五大成员:属性、方法、构造器、代码块、内部类内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

  1. class Outer{
  2. // 外部类
  3. class Inner{
  4. // 内部类
  5. }
  6. }
  7. class Other{
  8. // 外部其它类
  9. }

内部类的分类

1. 定义在外部类的局部位置上(方法/代码块内):A. 局部内部类(有类名)B. 匿名内部类(没有类名,重点)
2. 定义在外部类的成员位置上:A. 成员内部类(没用static修饰)B. 静态内部类(使用static修饰)

局部内部类

1. 可以直接访问外部类的所有成员,包括私有的。
2. 不能添加访问修饰符,因为它相当于一个局部变量,局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量也可以使用final。
3. 作用域:仅仅在定义它的方法或代码块中。
4. 外部类 访问 局部内部类的成员:创建对象再访问(必须在作用域内)。
5. 局部内部类 访问 外部类成员:直接访问。

  1. public class Outer {
  2. private int n1 = 10;
  3. public void say(){
  4. int n3 = 30;
  5. class Inner01{
  6. int n2 = n1; //直接访问外部类成员
  7. public void show(){
  8. System.out.println("调用局部内部类的方法");
  9. }
  10. }
  11. Inner01 a = new Inner01();
  12. a.show();
  13. //先创建对象再访问局部内部类的成员
  14. }
  15. Inner01 b = new Inner01(); //报错,已经不在局部内部类的作用域中了
  16. }

6. 外部其它类不能访问 局部内部类(因为 局部内部类相当于一个局部变量)。
7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用 外部类名.this.成员 去访问,其中 外部类名.this 相当于外部类

  1. public class Outer {
  2. private int n1 = 10;
  3. private static String name = "张三";
  4. public void say(){
  5. int n1 = 30;
  6. class Inner01{
  7. int n1 = 1000;
  8. public void show(){
  9. System.out.println(n1); //输出 1000,就近原则
  10. System.out.println(this.n1); //输出 1000,用this还是就近原则
  11. System.out.println(Outer.this.n1); //输出 10,外部类定义的变量
  12. System.out.println(Outer.n1); //报错
  13. }
  14. }
  15. Inner01 a = new Inner01();
  16. a.show();
  17. }
  18. }
  19. class Others{
  20. Inner01 a = new Inner01();
  21. //报错,外部其他类 不能访问 局部内部类
  22. }

匿名内部类

定义在外部类的局部位置,并且没有类名。(1)本质是类 (2)该类没有名字 (3)同时还是一个对象。

  1. new 类或接口(参数列表){
  2. //构造器
  3. 类体
  4. }; //注意返回一个指向该类或接口的实例
  1. 接口 ```java / 需求:用IA接口创建对象
    传统方法:写一个类实现该接口,并创建对象。
    但现在的要求是:创建的类只使用一次,后面就不再使用了。 可以使用 匿名内部类 来简化开发。
    / interface IA{
    void say(); }

public class Outer {
public void create(){ //写在方法内
IA tiger = new IA(){ //tiger为指向该接口的实例 @Override
public void say() {
System.out.println(“老虎嗷嗷叫”);
}
}; //匿名内部类,tiger的编译类型为IA,tiger的运行类型就是匿名内部类(也就是 Outer$1)
} }

  1. **实际上Jdk底层会给 匿名内部类 分配一个类名,分配的方法为 外部类类名+$(可用tigergetClass方法查看),然后立即创建一个 Outer$1实例,并且把地址赋给 tiger。**
  2. ```java
  3. class Outer$1 implements IA {
  4. @override
  5. public void cry(){
  6. System.out.println("老虎嗷嗷叫");
  7. }
  8. }
  1. ```java public class Outer { public void create(){
    1. Person1 a = new Person1("Jack") {
    2. @Override
    3. public void say() {
    4. System.out.println("在匿名内部类重写父类方法");
    5. }
    6. public void hello(){
    7. System.out.println("调用匿名内部类独有的方法");
    8. } //无法调用,因为编译类型为Person1,不能调用子类独有的方法
    9. };
    10. //不加大括号就是正常的创建类,运行类型为Person1,而加了就是匿名内部类
    11. a.say();
    12. System.out.println(a.getClass()); //输出a的运行类型,为 Outer$1
    } }

class Person1 { private String name; public Person1(String name) { this.name = name; } public void say(){ } }

  1. ```java
  2. /*
  3. a的编译类型为 Person1
  4. a的运行类型为 Outer$1(也就是整个匿名内部类)
  5. 底层会创建一个Person1实例 Outer$1,并返回给a
  6. class Outer$1 extends Person1{ // 底层创建的类
  7. @Override
  8. public void say() {
  9. System.out.println("在匿名内部类重写父类方法");
  10. }
  11. }
  12. Outer$1 XXX = new Outer$1("Jack");
  13. Person1 a = XXX; //XXX被销毁,Outer$1也被销毁
  14. */

注意事项

  1. 匿名内部类既是一个类的定义,同时它本身也是一个对象(本身就是个返回对象)。因此从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以直接调用匿名内部类方法
    1. new Person1("Jack") {
    2. @Override
    3. public void say() {
    4. System.out.println("在匿名内部类重写父类方法");
    5. }
    6. }.say(); //可以new之后直接调用
    3. 可以直接访问外部类的所有成员,包含私有的。
    4. 不能添加访问修饰符,因为它的地位就是一个局部变量。
    5. 作用域:仅仅在定义它的方法或代码块中。
    6. 匿名内部类 访问 外部类成员:直接访问。
    7. 外部其它类 不能访问 匿名内部类(因为匿名内部类建立之后就销毁了)。
    8. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 外部类名.this.成员 去访问。

    实例

    当作实参直接传递,简洁高效。 ```java interface IL{
    void show(); }

public class Outer{
public static void f1(IL il){
il.show(); }
public static void main(String[] args) {
f1(new IL() {
// 把匿名内部类当作参数导入
@Override
public void show() {
System.out.println(“导入匿名内部类”);
}
});
} }

  1. **对比一下传统方法**
  2. ```java
  3. public class Outer{
  4. public static void f1(IL il){
  5. il.show();
  6. }
  7. public static void main(String[] args) {
  8. Picture a = new Picture(); // 创建一个对象
  9. f1(a); // 利用接口的多态
  10. }
  11. }
  12. interface IL{
  13. void show();
  14. }
  15. class Picture implements IL{
  16. // 定义一个类实现接口
  17. @Override
  18. public void show() {
  19. System.out.println("传统方法");
  20. }
  21. }

成员内部类

成员内部类定义在外部类的成员位置,并且没有static修饰。
1. 可以直接访问外部类的所有成员包含私有的。
2. 可以添加任意访问修饰符,因为它的地位就是一个成员。
3. 作用域:和外部类的其他成员一样,为整个类。
4. 成员内部类 访问 外部类成员:直接访问。
5. 外部类 访问 成员内部类:创建对象再访问。

  1. class Outer{
  2. private int n1 = 10;
  3. class inner{
  4. public void show(){
  5. System.out.println(n1); //内部成员类 调用 外部类(私有也可以访问)
  6. }
  7. }
  8. public void say(){
  9. inner a = new inner(); // 先创建对象
  10. a.show();
  11. // 外部类 调用 内部成员类
  12. }
  13. }

6. 外部其它类 访问 成员内部类
第一种方式:把 Inner01 看作 Outer01 的成员,Outer01.Inner01 为编译类型,用外部类实例new

  1. class Others{
  2. Outer01 outer = new Outer01();
  3. Outer01.Inner01 inner = outer.new Inner01();
  4. //相当于把 Inner01当作 Outer01的成员
  5. }

第二种方式:在外部类中编写一个方法,可以返回 Inner01 对象

  1. public Inner01 getInner01(){
  2. Inner01 a = new Inner01();
  3. return a;
  4. } //编写方法
  5. class Others{
  6. Outer01 outer01 = new Outer01();
  7. Outer01.Inner01 inner01 = outer01.getInner01(); //用对象实例调用方法
  8. }
  1. 如果外部类和内部类的成员重名时,内部类访问遵循就近原则,如果想访问外部类的成员,则可以使用 外部类名.this.成员 去访问

    静态内部类

    静态内部类定义在外部类的成员位置,并且有static修饰。
    1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    2. 可以添加任意访问修饰符,因为它的地位就是一个成员。
    3. 作用域:同其他的成员,为整个类体。
    4. 静态内部类 访问 外部类:直接访问所有静态成员。
    5. 外部类 访问 静态内部类:先在外部类中创建对象再访问。
    6. 外部其它类 访问 静态内部类:
    第一种方法:因为是静态内部类,可以通过类名直接访问(满足访问权限)。
    1. Outer01.Inner01 inner01 = new Outer01.Inner01(); //直接用类名new
    第二种方法:编写一个方法,可以返回静态内部类的对象实例。 ```java public Inner01 create(){
    return new Inner01(); //返回一个 静态内部类的实例 } class Others{ Outer01 a = new Outer01();
    Outer01.Inner01 inner01 = a.create(); //调用方法返回实例 }

```

  1. 如果外部类和内部类的成员重名时,内部类访问遵循就近原则,如果想访问外部类的成员,则可以使用 外部类名.成员 去访问(因为 外部类名.this 是对象名,而静态变量可以用类名访问)