Java面向对象

面向对象概念

面向过程和面向对象

面向过程的思维模式
面向过程的思维模式是简单的线性思维
思考问题首先陷入第一步做什么、第二步做什么的细节中。这种思维模式适合处理简单的事情,比如:上厕所。
如果面对复杂的事情,这种思维模式会陷入令人发疯的状态!比如:如何造神舟十号!

面向对象的思维模式
面向对象的思维模式说白了就是分类思维模式
思考问题首先会解决问题需要哪些分类,然后对这些分 类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
这样就可以形成很好的协作分工。比如:设计师分了10个类,然后将10个类交给了10个人分别进行详细 设计和编码!

显然,面向对象适合处理复杂的问题,适合处理需要多人协作的问题!
如果一个问题需要多人协作一起解决,那么你一定要用面向对象的方式来思考!
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整 个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。

OOP

Java的编程语言是面向对象的,采用这种语言进行编程称为面向对象编程
(Object-Oriented Programming, OOP)。

什么是面向对象

面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。

  • 抽象:

    ​ 忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了 解全部问题,而只是选择其中的一部分,暂时不用关注细节。
    ​ 也就是将多个物体共同点归纳出来

  • 三大特性:

    • 封装:

      ​ 是对象和类概念的主要特性。封装是把过程和数据包围起来,对数据的访 问只能通过指定的方式。

    • 继承

      ​ 是一种联结类的层次模型,并且允许和支持类的重用,它提供了一种明确表述共性的方法。

    • 多态

      ​ 多态性是指允许不同类的对象对同一 属性/方法 作出不同响应。
      ​ 多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

类与对象的关系

是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物.

对象是抽象概念的具体实例

Student s = new Student(1L,"tom",20); 
s.study();
Car c = new Car(1,"BWM",500000); 
c.run();

对象s就是Student类的一个实例,对象c就是Car类的一个具体实例,能够使用的是具体实例,而不是类。
类 只是给对象的创建提供了一个参考的模板而已.
但是在java中,没有类就没有对象,然而类又是根据具体的功能需求,进行实际的分析,最终抽象出来的.

对象和引用的关系

引用 "指向" 对象

使用类型、数组类型、接口类型声明出的变量,都可以指向对象,这种变量就是引用类型变量
简称引用。

在程序中,创建出对象后,直接使用并不方便,所以一般会用一个引用类型的变量去接收这个对象
这个就是 所说的引用指向对象.

对象的创建

new

使用new关键字创建对象
使用new关键字创建的时候,除了分配内存空间之外,
还会给 创建好的对象 进行默认的初始化以及对类中构造器的调用。

Student s = new Student();

1)为对象分配内存空间,将对象的实例变量自动初始化默认值。(实例变量的隐式赋 值)
2)如果代码中实例变量有显式赋值,那么就将之前的默认值覆盖掉。(之后可以通过例子看到这个现象)
3)调用构造器
4)把对象内存地址值赋值给变量。( = 号赋值操作)

构造器

类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。

特点:

1.必须和类的名字相同

2.必须没有返回类型,也不能写void

作用:

1.使用new创建对象的时候 使用类的构造器

2.构造器中的代码执行后,可以给对象中的属性 初始化赋值

```java
public class Student{    
   private String name;    
   public Student(){        
      name = "tom";  
   }
}
```

构造器重载

除了无参构造器之外,很多时候我们还会使用有参构造器,在创建对象时候可以给属性赋值.

public class Student{    
   private String name;
   public Student(){        
      name = "tom";  
   }
   public Student(String name){        
      this.name = name;
   } 
}

构造器间的调用

使用this关键字,在一个构造器中可以调用另一个构造器的代码。
注意:this的这种用法不会产生新的对象,只是调用了构造器中的代码而已
一般情况下只有使用new关键字才会创建新对象。

public class Student{    
   private String name;    
   public Student(){        
      this();
 }
   public Student(String name){        
      this.name = name;
   } 
}

默认构造器

在java中,即使我们在编写类的时候没有写构造器,那么在编译之后也会自动的添加一个无参构造器,这个无参构造器也被称为默认的构造器

内存分析

JAVA程序运行的内存分析

  • 栈 stack:
  1. 每个线程私有,不能实现线程间的共享!
  2. 局部变量放置于栈中。
  3. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!
  • 堆 heap:
  1. 放置new出来的对象!
  2. 堆是一个不连续的内存空间,分配灵活,速度慢!
  • 方法区(堆的特殊区域):
  1. 被所有线程共享!
  2. 用来存放程序中永远是不变或唯一的内容。
    (类代码信息、静态变量、字符串常量)

this

this的含义

this在类中表示当前类将来创建出的对象

public class Student{    
   private String name;    
   public Student(){
       System.out.println("this = "+this);  
   }
   public static void main(String[] args){        
      Student s = new Student();
      System.out.println("s = "+s);  
   }
}

this和s打印的结果是一样的,内存地址也是一样的
s 是 在对象的 外部 执行对象
this是 在对象的 内部 执行对象本身.

this并不是只有一个,每个对象中都有一个属于自己的this

this的作用

  • 区分实例变量和局部变量
public class Student{    
   private String name;
   public void setName(String name){        
      //this.name表示类中的属性name        
      this.name = name;
 	} 
}
  • 调用类中的其他方法
public class Student{    
   private String name;
   public void setName(String name){        
      //this.name表示类中的属性name        
      this.name = name;
 	} 
}
  • 调用类中的其他构造器

注:this的这种用法,只能在构造器中使用
普通的方法是不能用的.并且这局调用的代码只能出现在构造器中的第一句

public class Student{    
   private String name;    
   public Student(){
       //调用一个参数的构造器,并且参数的类型是String        
      this("tom");
 }
   public Student(String name){        
      this.name = name;
 	} 
}

面向对象的三大特性

封装

含义

在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部是可见的,哪些特性 用于表示内部状态。
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。

步骤

  1. 使用private 修饰需要封装的成员变量。

  2. 提供一个公开的方法设置或者访问私有的属性

​ 设置 set()方法,命名格式: set属性名(); 属性的首字母要大写
​ 访问 get()方法,命名格式: get属性名(); 属性的首字母要大写

//对象能在类的外部"直接"访问
public class Student{
   public String name;
   public void println(){
       System.out.println(this.name);  
   }
}
public class Test{
   public static void main(String[] args){        
      Student s = new Student();
       s.name = "tom";  
	}
}

通过 private 关键字可以将类中的数据隐藏起来
在 类的外部需要访问这些私有属性,那么可以在类中提供对于的get和set方法,以便让用户在类的外部间接的访问到私有属性

//set负责给属性赋值
//get负责返回属性的值 
public class Student{    
	private String name;
   public void setName(String name){        
      this.name = name;
 	}
   public String getName(){        
      return this.name;  
   }
}
public class Test{
   public static void main(String[] args){        
      Student s = new Student();
      s.setName("tom");
      System.out.println(s.getName());
   }
}

意义

  1. 提高程序的安全性,保护数据。
    (可以在set方法中增加if条件语句,安全判断)

  2. 隐藏代码的实现细节

  3. 统一用户的调用接口

  4. 提高系统的可维护性

  5. 便于调用者调用。

良好的封装,便于修改内部代码,提高可维护性。
良好的封装,可进行数据完整性检测,保证数据的有效性。

继承

继承概述

继承本质:
对某一批类的抽象,从而实现对现实世界更好的建模。
继承的作用:
继承的本质在于抽象。
类是对对象的抽象,继承是对某一批类的抽象。
为了提高代码的复用性。

【注】JAVA中类只有单继承,没有多继承! 接口可以多继承!

继承语法

public class student extends Person{
}
  1. 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。

  2. 继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。
    子类继承父类,使用关键字extends来 表示。

  3. 类和和父类之间,从意义上讲应该具有"is a"的关系.

  4. 类和类之间的继承是单继承

  5. 父类中的属性和方法可以被子类继承

​ 子类中继承了父类中的属性和方法后,在子类中能不能直接使用这些属性和方法,
是和这些属性和方法原有的修饰符(public protected default private)相关的。

  1. 父类中的构造器是不能被子类继承的。
    但是子类的构造器中,会隐式的调用父类中的无参构造器(默认使用 super关键字)。

Object

Ctrl + H 可以查看继承关系

java中的每一个类都是"直接" 或者 "间接"的继承了Object类.

在Object类中,提供了一些方法被子类继承,那么就意味着,在java中,任何一个对象都可以调用这些被继承 过来的方法。(因为Object是所以类的父类)
例如:toString方法、equals方法、getClass方法等

Super

子类继承父类之后,在子类中可以使用super 访问 或 调用 父类中的属性和方法。

  • 访问父类中的属性
public class Person{
   protected String name = "zs"; 
}
public class Student extends Person{    
   private String name = "lisi";    
   public void test(String name){
       System.out.println(name);	//形参nanme
       System.out.println(this.name);	//"lisi"
       System.out.println(super.name);  //"zs"
   }
}
  • 调用父类中的方法
public class Person{    
   public void print(){
       System.out.println("Person");  
   }
}
public class Student extends Person{    
   public void print(){
       System.out.println("Student");  
   }
   public void test(){        
      print();
       this.print();        
      super.print();  
   }
}
  • 调用父类的构造器
public class Person{ 
}
public class Student extends Person{
   //new子类对象时,子类构造器中会隐式的调用父类的无参构造器    
   public Student(){  
   	//super();
   }
}

不管是显式还是隐式的父类的构造器,super语句一定要出现在子类构造器中第一行代码。
所以this和super不可能同时使用它们调用构造器的功能,因为它们都要出现在第一行代码位置。

  • 注意:
  1. 用super调用父类构造方法,必须是构造方法中的第一个语句
  2. super只能出现在子类的方法或者子类的构造方法中。
  3. super 和 this 不能同时调用构造方法
    (因为this也是在构造方法的第一个语句)
  • super 和 this 的区别:

    1. 代表的事物不一样:

    super:代表父类对象的引用空间
    this:代表所属方法的调用者对象本身

    1. 使用前提不一致:

    super:只能在继承的条件下才能使用。
    this:没有继承也可以使用。

    1. 调用构造方法:

    super:调用的父类的构造方法。
    this:调用本类的构造方法。

方法重写

Alt + Insert @Override

​ 1. 方法重写只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被重写.

  1. 静态方法不能重写

    ​ 父类的静态方法不能被子类重写为非静态方法;//编译出错

    ​ 父类的非静态方法不能被子类重写为静态方法;//编译出错

    ​ 子类可以定义与父类的静态方法同名的静态方法(但是这个不是覆盖)

私有方法不能被子类重写。
子类继承父类后,是不能直接访问父类中的私有方法的,那么就更谈不上 重写了。

public class Person{    
   private void run(){
   } 
}
//编译通过,但这不是重写,只是俩个类中分别有自己的私有方法 
public class Student extends Person{
   private void run(){
   } 
}
  1. 重写语法注意点:

    1. 方法名必须相同
    2. 参数列表必须相同
    3. 访问控制修饰符可以被扩大,但是不能被缩小: public protected default private
    4. 抛出异常类型的范围可以被缩小,但是不能被扩大 ClassNotFoundException ---> Exception
    5. 返回类型要么相同,要么子类重写后的方法返回类型
      必须是父类方法返回类型的子类型
  2. 重写意义:

    ​ 子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需 要把方法进行重写。

多态

概述

多态可以让我们不用关心某个对象到底是什么具体类型,就可以使用该对象的某些方法,从而实现更加灵活的编程,提高系统的可扩展性。

一个对象的实际类型(new 时的,后面跟的类)是确定的
但是 指向这个对象的 引用的类型,却可以是这对象实际类型的任意父类型

  • 一个父类引用 可以指向它的任何一个 子类对象:
Object o = new AnyClass(); 
Person p = null;
p = new Student(); 
p = new Teacher(); 
p = new Person();
  • 调用子类重写的方法:
public class Person{    
   public void run(){
   } 
}
public class Student extends Person{    
   public void run(){
       //重写run方法  
   }
}
//调用到的run方法,是Student中重写的run方法 main:
Person p = new Student(); 
p.run();

new子类对象给父类引用,利用父类引用调用a方法
如果a方法在子类中没有重写,那么就是调用的是子类继承父类的a方法,
如果子类中重写了,那么调用的就是重写之后的方法。

  • 调用子类特有的方法:
public class Person{    
   public void run(){
   } 
}
public class Student extends Person{    
   public void go(){
   }
}
//需要把变量p的类型进行转换 
Person  p = new Student();
Student s = (Student)p; 
s.go();
或者
//注意这种形式前面必须要俩个小括号 
((Student)p).go();   

多态存在的条件

  1. 有继承关系

  2. 子类重写父类方法

    ​ 以下父类中的三种类型的方法是没 有办法表现出多态特性的(因为不能被重写):

    1. static方法,因为被static修饰的方法是属于类的,而不是属于实例的
    2. final方法,因为被final修饰的方法无法被子类重写
    3. private方法和protected方法
      前者是因为被private修饰的方法对子类不可见
      后者是因为尽管被 protected修饰的方法可以被子类见到,也可以被子类重写
      但是它是无法被外部所引用的,一个 不能被外部引用的方法,怎么能谈多态呢
  3. 父类引用指向子类对象

instanceof

  1. 编译是否通过

System.out.println(x instanceof Y);
该代码能否编译通过,主要是看 声明变量x的类型 和 Y 是否存在子父类的关系。
有子父类关系就编译通过, 没有子父类关系就是编译报错.
之后学习到的接口类型和这个是有点区别的。

  1. 输出结果 ture/false

System.out.println(x instanceof Y);
输出结果是true还是false,
变量 x所指向的 对象实际类型 Y类型的"子类型"
输出ture

变量 x所指向的 对象实际类型 不是Y类型的"子类型"
输出false

Object o = new Person();
System.out.println(o instanceof Student);//false
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false

修饰符

static修饰符

修饰变量

在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。
static不能修饰局部变量

静态变量和非静态变量的区别:
静态变量属于类的,"可以"使用类名来访问(也可以用对象访问)
非静态变量是属于对象的,"必须"使用对象来访问.

在加载类的过程中为静态变量分配内存,实例变量在创建对象时分配内存
所以静态变量可以使用类名来 直接访问,而不需要使用对象来访问.

public class Student{
   private static int age;    
   private double score;
   public static void main(String[] args) {        
       Student s = new Student();
       //推荐使用类名访问静态成员
       System.out.println(Student.age);
       System.out.println(s.age);
       System.out.println(s.score);  
   }
}

修饰方法

在类中,使用static修饰的成员方法,就是静态方法,反之为非静态方法。

静态方法和非静态方法的区别:
静态方法数属于类的,"可以"使用类名来调用(也可以用对象访问)
静态方法"不可以"直接访问类中的非静态变量和非静态方法
但是"可以"直接访问类中的静态变量和静态方法
父类的静态方法可以被子类继承,但是不能被子类重写

非静态方法是属于对象的,"必须"使用对象来调用
非静态方法"可以"直接访问类中的非静态变量和非静态方法
也"可以"直接访问类中的静态变量和静态方法
父类的非静态方法不能被子类重写为静态方法

static代码块

  1. 类中可以编写 代码块 和 静态代码块
public class Person {  
   {
       //代码块(匿名代码块)  
   }
   static{
       //静态代码块  
   }
}
  1. 匿名代码块和静态代码块的执行:
    匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前
    同时匿名代码块在每次创建对象的时候都会自动执行.

    ​ 静态代码块是在类加载完成之后就自动执行,并且只执行一次

  2. 匿名代码块和静态代码块的作用
    匿名代码块的作用是给对象的成员变量初始化赋值
    但是因为构造器也能完成这项工作,所以匿名代码块 使用的并不多。

    ​ 静态代码块的作用是给类中的静态成员变量初始化赋值

  3. 创建和初始化对象的过程

    1. 类加载,同时初始化类中静态的属性
    2. 执行静态代码块
    3. 分配内存空间,同时初始化非静态的属性(赋默认值)
    4. 调用Student的父类构造器
    5. 对Student中的属性进行显示赋值(如果有的话)
    6. 执行匿名代码块
    7. 执行构造器
    8. 返回内存地址
  4. 静态导入
    静态导包就是java包的静态导入,用import static代替import静态导入包是JDK1.5中的新特性。 意思是导入这个类里的静态方法。
    好处:这种方法的好处就是可以简化一些操作,例如打印操作System.out.println(…);就可以将其写入一 个静态方法print(…),在使用时直接print(…)就可以了。
    但是这种方法建议在有很多重复调用的时候使用,如果仅有一到两次调用,不如直接写来的方便。

import static java.lang.Math.random; 
import static java.lang.Math.PI;
public class Test {
   public static void main(String[] args) {        
       //之前是需要Math.random()调用的
       System.out.println(random());        System.out.println(PI);
   } 
}

final修饰符

修饰变量

用final修饰的变量表示常量,只能被赋一次值
其实使用final修饰的变量也就成了常量了,因为值不会再变了。

【修饰局部变量】

public class Person{
   public void print(final int a){
       //编译报错,不能再次赋值,传参的时候已经赋过了        
       a = 1;
   } 
}
public class Person{    
   public void print(){        
      final int a;
      a = 1;
      //编译报错,不能再次赋值        
      a = 2;
 	}
}

【修饰实例变量】

public class Person{    
   private final int a; 
}
只有一次机会,可以给此变量a赋值的位置: 
	声明的同时赋值
	匿名代码块中赋值
	构造器中赋值(类中出现的所有构造器都要写)

【修饰静态变量】

public class Person{
   private static final int a; 
}
只有一次机会,可以给此变量a赋值的位置: 
	声明的同时赋值
	静态代码块中赋值

【修饰引用变量】

main:
final Student s = new Student(); 
//编译通过,可以更改对象的属性
s.setName("tom"); 
s.setName("zs");
//编译报错,不能修改引用s指向的内存地址 
s = new Student();

修饰方法

	用final修饰的方法可以被继承,但是**不能被子类重写**。
​		例如:每个类都是Object类的子类,继承了Object中的众多方法,在子类中可以重写toString方法、equals方法等
​		但是不能重写getClass方法、wait方法等,因为这些方法都是使用fianl修饰的。

我们也可以定义final修饰的方法:

public class Person{
   public final void print(){
   } 
}

public class Student extends Person{
   //编译报错,不能重写父类中被final修饰的方法
   public void print(){
   } 
}

修饰类

	用final修饰的类**不能被继承**,没有子类。
		例如:我们是无法写一个类去继承String类

我们也可以定义final修饰的类:

public final class Action{ 
}
//编译报错
public class Go extends Action{ 
}

abstract修饰符 --> 抽象类

抽象类

abstract修饰符
如果修饰方法,那么该方法就是抽象方法;
如果修饰类,那 么该类就是抽象类。

抽象类和抽象方法的关系

​ 抽象类中可以没有抽象方法
​ 但是有抽象方法的类一定要声明为抽象类

语法

public abstract class Action{
   //声明方法的时候,加上abstract修饰符,并且去掉方法的大口号,同时结尾加上分号,该方法就是抽象方法。
   public abstract void doSomething(); 
}
//这是一个普通的
public void doSomething(){
   ...        
}

特点

抽象类:
不能new对象,只能靠 让子类继承 的。
抽象类中可以有普通方法
抽象方法:
只有方法的声明,没有方法的实现,它是用来 让子类实现 的。

注:子类继承抽象类后,需要实现抽象类中没有实现的抽象方法
否则这个子类也要声明为抽象类

作用

​ 抽象类和抽象方法起到一个框架作用。很方便后期的调用和重写
​ 抽象方法是为了程序的可扩展性。重写抽象方法时即可实现同名方法但又非同目的的要求。

接口

概述

​ 接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。
​ 全面地专业地实现了: 规范和具体实现的分离。
接口中所有方法都是抽象方法。
​ 接口是完全面向规范的,规定了一批类具有的公共方法规范。
​ 接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的系统之间模块之间的接口定义 好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。

接口和抽象类的区别

声明:抽象类 abstract class
接口 interface

继承:抽象类是用来被继承的,java中的类是单继承。
类A继承了抽象类B,那么类A的对象就属于B类型了,可以使用多态 一个父类的引用,可以指向这个父类的任意子类对象
注:继承的关键字是extends

​ 接口是用来被类实现的,java中的接口可以多继承后再实现。
​ 类A实现接口B、C、D、E..,那么类A的对象就属于B、C、D、E等类型了,可以使用多态 一个接口的引用,可以指向这个接口的任意实现类对象
​ 注:实现的关键字是implements

接口中的方法都是抽象方法

public interface Action{
   public abstract void run();    
   //默认就是public abstract修饰的
   void test();
   public void go(); 
}

接口中的变量都是静态常量(pubilc static final修饰)

接口中可以不写任何属性,但如果写属性了,该属性静态常量。
注:可以直接使用 接口名 访问其属性。因为是public static修饰的
注:声明的同时就必须赋值.(因为接口中不能编写静态代码块)

public interface Action{
   public static final String NAME = "tom";    
   //默认就是public static final修饰的
   int AGE = 20; 
}
main:
System.out.println(Action.NAME); System.out.println(Action.AGE);

一个(实现)类可以实现多个接口

public class Student implements A,B,C,D{
   //Student需要实现接口A B C D中所有的抽象方法
   //否则Student类就要声明为抽象类,因为有抽象方法没实现 
}
main:
A s1 = new Student();	//s1只能调用接口A中声明的方法以及Object中的方法
B s2 = new Student();	//s2只能调用接口B中声明的方法以及Object中的方法
C s3 = new Student();	//s3只能调用接口C中声明的方法以及Object中的方法
D s4 = new Student();	//s4只能调用接口D中声明的方法以及Object中的方法

一个接口可以继承多个父接口

public interface A{
   public void testA(); 
}
public interface B{
   public void testB(); 
}
//接口C把接口A B中的方法都继承过来了
public interface C extends A,B{    
   public void testC();
}
//Student相当于实现了A B C三个接口,需要实现所有的抽象方法 
//Student的对象也就同时属于A类型    B类型    C类型
public class Student implements C{
   public viod testA(){}
   public viod testB(){}
   public viod testC(){} 
}


main:
C o = new Student();
System.out.println(o instanceof A);//true
System.out.println(o instanceof B);//true
System.out.println(o instanceof C);//true
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//编译报错
System.out.println(o instanceof String);

System.out.println(o instanceof X);

如果o是一个接口类型声明的变量,那么只要X不是一个final修饰的类,该代码就能通过编译,
结果为ture:变量o指向的 对象的实际类型,是X的 子类 或者 实现类。
结果为false:变量o指向的 对象的实际类型,不是是X的 子类 或者 实现类。

接口的作用

  1. Java接口中的成员变量默认都是public,static,final类型的(都可省略),必须被显示初始化,即接口中的成员变量为常量(大写,单词之间用"_"分隔)
  2. Java接口中的方法只能是抽象方法(默认是public,abstract类型的(都可省略)),没有方法体,不能被实例化
  3. 接口中没有构造方法,接口不能new对象(被实例化)
  4. 一个接口不能实现(implements)另一个接口,但它可以继承多个其它的接口
  5. Java接口必须通过类来实现它的抽象方法,它必须实现接口中的所有抽象方法,否则这个类必须声明为抽象类
  6. 一个类实现多个接口,间接的实现了多继承.

内部类

概述

内部类就是在一个类的内部在定义一个类
比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。

内部类分为四种:

  1. 成员内部类

  2. 静态内部类

  3. 局部内部类

  4. 匿名内部类

实例内部类(非静态内部类)

注:实例内部类中不能写静态属性和方法

  • 要想访问实例内部类中的内容,必须通过外部类对象来实例化内部类。
public class Outer {    
   private int id;    
                    
   public void out(){
       System.out.println("这是外部类方法");  
   }
   class Inner{               
      public void in(){
           System.out.println("这是内部类方法");      
      }
   } 
}
public class Test{
   public static void main(String[] args) {        
       //实例化成员内部类分两步
       //1、实例化外部类
       Outer outObject = new Outer();        
       //2、通过外部类调用内部类
       Outer.Inner inObject = outObject.new Inner();        
      //测试,调用内部类中的方法
      inObject.in();//打印:这是内部类方法  
   }
}
  • 实例内部类能够访问外部类所有的属性和方法
    原理就是在通过外部类对象实例化内部类对象时,外部类对象 把自己的引用传进了内部类
public class Outer {    
   private int id;    
   public void out(){
       System.out.println("这是外部类方法");  
   }
   class Inner{    
       public void in(){
           System.out.println("这是内部类方法");      
       }
       //内部类访问外部类私有的成员变量        
      public void useId(){
           System.out.println(id+3);。      
      }
       //内部类访问外部类的方法        
      public void useOut(){            
         out();
     }  
   }
}
public class Test{
   public static void main(String[] args) {        
      //实例化成员内部类分两步
       //1、实例化外部类
       Outer outObject = new Outer();        
      //2、通过外部类调用内部类
       Outer.Inner inObject = outObject.new Inner();        
      //测试
       inObject.useId();//打印3,因为id初始化值为0,0+3就为3,其中在内部类就使用了 外部类的私有成员变量id。
       inObject.useOut();//打印:这是外部类方法  
   }
}
  • 如果实例内部类中的变量名和外部类的成员变量名一样
    要通过通过 Outer.this. 访问外部类的属性和方法
    通过 this. 访问内部类成员属性
public class Outer {
   private int id;//默认初始化0    
   public void out(){
       System.out.println("这是外部类方法");  
   }
	class Inner{    
       private int id=8;    //这个id跟外部类的属性id名称一样。  
       public void in(){            
           System.out.println("这是内部类方法");      
       }
       public void test(){
           System.out.println(id);//输出8,内部类中的变量会暂时将外部类的成员 变量给隐藏
           /*如何调用外部类的成员变量:
	通过Outer.this,想实例化内部类对象,就必须通过外部类对象,当外部类对象来new出内部类对象时,会
	把自己(外部类对象)的引用传到了内部类中,所以内部类就可以通过Outer.this来访问外部类的属性和方法
	这里由于有两个相同的属性名称,所以需要显示的用Outer.this来调用外部类的属性,平常如果属性名不重复,那么我们在内部类中调用外部类的属性和方法时,前面就隐式的调用了Outer.this。
           */
               System.out.println(Outer.this.id);//输出外部类的属性id。也 就是输出0
     }    
   }  
}

静态内部类

使用static修饰的 内部类 就叫静态内部类。

注意:

  1. 静态内部类能够直接被外部类给实例化,不需要使用外部类对象
  2. 静态内部类中,不能访问非静态的变量或方法(自身或者外部类的属变量或方法都不行)
  3. 静态内部类中可以声明非静态的变量或方法,也可以声明静态的变量或方法
    但非静态内部类中不可以声明静态的变量或方法
public class StaticInnerClassTest {    
   private String name;
   private static int age;    
   public void run(){}
   public static void go(){}    
   //外部类访问静态内部类
   public void test(){
       StaticInnerClass sic = new StaticInnerClass(); 
      //静态的内部类不需要依赖 外部类,所以不用this
       sic.name = "tom";
       sic.test1("jack");
       StaticInnerClass.age=10;
       StaticInnerClass.test2("xixi");  
   }
   private static class StaticInnerClass{
       private String name;
       private static int age;
       public void test1(String name){
           System.out.println(name);
           System.out.println(this.name);
           System.out.println(StaticInnerClass.age);
           System.out.println(StaticInnerClassTest.age);
           //System.out.println(StaticInnerClassTest.this.name);静态类不能访 问非静态属性
           StaticInnerClassTest.go();
           //StaticInnerClassTest.this.run();静态类不能访问非静态方法
       }
      
       public static void test2(String name){            
           //只能访问自己和外部类的静态属性和方法            
           System.out.println(name);
           //System.out.println(this.name);静态方法里面连自己类的非静态属性都不能 访问
           System.out.println(StaticInnerClass.age);
           System.out.println(StaticInnerClassTest.age);
           //System.out.println(StaticInnerClassTest.this.name);静态方法不能 访问非静态属性
           StaticInnerClassTest.go();
           //StaticInnerClassTest.this.run();静态方法不能访问非静态方法 
       }
   } 
}

局部内部类

局部内部类是在一个方法内部声明的一个类

  • 局部内部类中可以访问外部类的成员变量及方法
    局部内部类中可以定义非静态变量不能定义静态变量

  • 局部内部类中如果要访问该内部类所在方法中的局部变量
    那么这个局部变量就必须是final修饰的

public class Outer {    
   private int id;
//在method01方法中有一个Inner内部类,这个内部类就称为局部内部类    
   public void method01(){
      final int cid = 3;    //这个就是局部变量cid。要让局部内部类使用,就得变为 final并且赋值,如果不使用final修饰,就会报错
      class Inner{
           public void in(){
               System.out.println("这是局部内部类");        
           }
           //内部类中的使用局部变量cid的方法            
           public void useCid(){                   
              System.out.println(cid);        
           }
     }  
   }
}

原因:
final修饰变量:变为常量,会在常量池中放着
如果不实用final修饰,当局部内 部类被实例化后,方法弹栈,局部变量随着跟着消失,这个时候局部内部类对象在想去调用该局部变 量,就会报错,
因为该局部变量已经没了,当局部变量用fanal修饰后,就会将其加入常量池中,即使方 法弹栈了,该局部变量还在常量池中呆着,局部内部类也就是够调用。
所以局部内部类想要调用局部变 量时,需要使用final修饰,不使用,编译度通不过。

  • 调用局部内部类中的方法

局部内部类只能在自己的方法中用
因为局部内部类相当于一个局部变量,出了方法就找不到了。

局部内部类不能通过外部类对象直接实例化,而是在方法中实例化出自己来
然后通过 引用调用自己类 中的方法。

public class Outer {    
   private int id;
   public void out(){
       System.out.println("外部类方法");  
   }
   public void method01(){        
      class Inner{
           public void in(){   
              System.out.println("这是局部内部类");        
           }
     }
		 //关键在这里,如需要在method01方法中自己创建内部类实例,然后调用内部类中的方法,等待 外部类调用method01方法,就可以执行到内部类中的方法了。
       Inner In = new Inner();        
      In.in();
 	} 
}

匿名内部类

  • 匿名对象
public class Test {
   public static void main(String[] args) {
      //只需要用一次       
      apple.eat();
      //这种就叫做匿名对象的使用,不把实例保存到变量中。        
      new Apple().eat();
 	} 
}
class Apple{
   public void eat(){
       System.out.println("我要被吃了");  
   }
}
  • 匿名内部类

只需要用一次,不需要在类中先定义一个内部类
等待需要用的时候,就临时实现这个内部类
因为用次数少,可能就这一次,那么这样写内部类,更方便。

注意点:

  1. 匿名内部类需要依托于其他类或者接口来创建
    但不能指定继承或者实现其他类或接口
    也不能被其他类所继承
  2. 匿名内部类的声明必须是在使用new关键字的时候
  3. 匿名内部中,我们不能写出其构造器,因为没有名字。
  4. 匿名内部中,除了重写上面的方法外,一般不会再写其他独有的方法,因为从外部不能直接调用到。(间接是调用到的)

【不使用匿名内部类实现接口】

public class Test {
   public static void main(String[] args) {
/*如果我们需要使用接口中的方法,我们就需要走3步,
      1、实现接口    
      2、创建实现接口类的实 例对象    
      3、通过对象调用方法
*/
		//第二步         
		Test02 test = new Test02(); 
      //第三步
      test.method();  
   }
}

//接口Test1
interface Test01{
   public void method(); 
}

//第一步、实现Test01接口
class Test02 implements Test01{    
   @Override
   public void method() {
       System.out.println("实现了Test接口的方法");  
   }
}

【使用匿名内部类实现接口】

public class Test {
   public static void main(String[] args) {
      
		//如果我们需要使用接口中的方法,我们只需要走一步,就是使用匿名内部类,直接将其 类的对象创建出来。
		new Test1(){
			public void method(){
			System.out.println("实现了Test接口的方法"); }
		}.method();
      
 	} 
}
interface Test1{
   public void method(); 
}

解析:
new Test1(){...}这个的作用就是将接口给实现了,只不过这里实现该接口的是一个匿名类,只能使用这一次,
将这个类new出来,就能获得一个实现了Test1接口的类的实例对象
通过该实例对象,就能调用该类中的方法了

posted @ 2021-02-17 13:36  feifan666  阅读(128)  评论(0)    收藏  举报