JAVASE_07天_@面向对象_extends与interface

本文深入讲解Java中的继承机制,探讨单继承与多实现的特点,分析抽象类与接口的区别及应用场景,帮助读者掌握面向对象编程的核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.继承
        1.提高了代码的复用性
        2.让类与类之间产生了关系,有了这个关系,才有了多态的特性

java语言中:
            1.java只支持单继承,不支持多继承, (接口与接口之间可以实现多继承)
因为多继承容易带来安全隐患:
当多个父类定义了相同的功能,当功能的内容不同时,子类对象不确定要运行哪一个
            但是java保留这种机制,并用另一种体现形式来完成表示.....多实现
            2.java支持多层继承,也就是一个继承体系




2.聚集
聚合:2个对象之间整体和部分的弱关系。生命周期不同步
组合:2个对象之间整体和部分的强关系。生命周期同步,或者说不能脱离整体而存在。
public class Person{
private Telephone tel;
private Hand hand = new Hand();
.......
}
class Telephone{
}
public class hand{
}



3.子父类变量的特点

/*
子父类出现后,类成员的特点:
 
类中成员:
1,变量。
2,函数。
3,构造函数。
 
1,变量
如果子类中出现非私有的同名成员变量时,
子类要访问本类中的变量,用this
子类要访问父类中的同名变量,用super。
 
super的使用和this的使用几乎一致。
this代表的是本类对象的引用。
super代表的是父类对象的引用。
*/
class  Fu{
     int  num =  4 ;
}
class  Zi  extends  Fu{
     int  num =  5 ;
     void  show(){
         System.out.println( this .num); //打印子类中的成员变量5
         System.out.println( super .num); //打印父类的成员变量4
        System.out.println(num);//打印父类的成员变量5,局部作用域覆盖,先this里找,没有会往super里找
         
     }
}
 
public  class  ExtendsDemo {
     public  static  void  main(String[] args){
         Zi c =  new  Zi();
         c.show();
     }
}



4.子父类函数的特点
方法前面权限修饰:public   >    默认(什么也没写的时候)      >      private
覆盖(重写)注意事项:
1,子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
2,静态只能覆盖静态。
记住大家:
重载:只看同名函数的参数列表(本类中)。
重写:子父类方法要一模一样。包括函数类型和返回类型(子父类之间)



5.子父类中构造函数的特点_子类实例化过程
 
在对子类对象进行初始化时,父类的构造函数也会运行,
那是因为子类的构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class  Super{
     int  i= 0 ;
     public  Super(String a){
         System.out.println( "A" );
         i= 1 ;   
     }
     public  Super(){
         System.out.println( "B" );
         i+= 2 ;
     }
}
class  Test  extends  Super{
     public  Test(String a){
         //此处隐藏super();
         System.out.println( "C" );
         i= 5 ;               
     }
     public  static  void  main(String[] args){
         int  i= 4 ;
         Super d= new  Test( "A" );
         System.out.println(d.i);
     }
}结果:B C 5

子类的构造函数因为继承的缘故所以必须带参数,
但是子类构造在第一行有super()会调用父类无参构造
如果没有,则编译失败
 
为什么子类一定要访问父类中的构造函数。
 
因为父类中的数据子类可以直接获取。所以子类对象在建立时,
需要先查看父类是如何对这些数据进行初始化的。
所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定

注意:super语句一定定义在子类构造函数的第一行。
为啥this();和super();不能同时存在,因为他们都的放在第一行,为啥第一行,因为需要先初始化.
 
 
 
子类的实例化过程。
 
结论:
子类的所有的构造函数,默认都会访问父类中空参数的构造函数。
因为子类每一个构造函数内的第一行都有一句 隐式super();<-----------super()是空参数

当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问父类中的构造函数。

class  Father{
     String nameF;
     Father(String nameF){
         this .nameF = nameF;
     }
     
}  
class  Children  extends  Father{
     String nameC;
     Children(String nameC) {
         super(nameC);
     }
}
 
当然:子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。
子类中至少会有一个构造函数会访问父类中的构造函数.

子类创建对象,加载子类.class文件前,先加载父类.class!
代码一:
class  Fu{
     int  num =  4 ;
}
class  Zi  extends  Fu{
     int  num =  5 ;
     void  show(){
         System.out.println(num); //结果是5   
          
     }
}------------------------------------------

         
                                                                    
代码二:
class  Fu{
     int  num =  4 ;
}
class  Zi  extends  Fu{
     void  show(){
         System.out.println(num); //结果是4        
     }
}-----------------------------------------------



6.final关键字-
       -------------继承的出现对于封装性是个弊端!
1,注意事项:
1.1.final关键字对于变量的存储区域是没有任何影响的

1.2编译器必须保证final变量在使用前被初始化赋值.
对于final修饰的成员变量,必须在构造中赋值,如果有多个构造,必须都赋值.可以赋值不同

2,final:最终。作为一个修饰符

1,可以修饰类,函数,变量
2,被final修饰的了类不可以被继承.为了避免被继承,被子类复写功能
3,被final修饰的方法不可被重写<---------抽象类中定义后不能不重写应用.模板方法模式
4,被final修饰的变量是一个常量,只能被赋值一次,即可以修饰成员变量,又可以修饰局部变量
作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。单词间通过_连接。
5,内部类定义在类中的局部位置时,
访问成员变量可以,
但是访问局部变量时,必须是被final修饰局部变量-------------????
为啥?
final关键字的目的就是为了保证内部类和外部函数对变量“认识”的一致性。

        3,赋值过程
public final int age = 25 ;常量
public static final age = 25 ;类常量
用static final声明类常量,
类常量必须在main( )方法的外部
如果被public修饰表示其他类也可以使用
要想分析这2个语句的不同看下面解析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  class  FinalOriginalTest {   
     private  final  int  a;   
     private  String name;   
     public  FinalOriginalTest(){   
         a =  3 ;   
      }   
     public  FinalOriginalTest(String name){   
         this .name = name;
        在这没有对常量初始化.a = 2;
      }   
     public  static  void  main(String[] args){   
          FinalOriginalTest ft =  new  FinalOriginalTest();
          FinalOriginalTest ft1 =  new  FinalOriginalTest( "hello" );
          
          System.out.println(ft.a);  //3
          System.out.println(ft1.a);  //2
      }   
}
<一>问题1:上面的程序能否编译通过?如果不能,请说明理由。 

解答:不能编译通过,可能没有初始化变量a。
因为对于final类型的成员变量的初始化,在构造方法中完成赋值
如果一个类有多个构造方法,就要保证在每个构造方法中都要完成对该final类型变量的初始化工作。
所以需要在public FinalOriginalTest(String name)构造方法中给a赋值。 

注意在上面修改后分别在2个构造中赋值不同,那么输出a的值也不同

注意:final可以用在类、方法、变量上。 
     1、final用在类上,表明当前类它不能被继承,没有子类 
     2、final用在方法上,表明当前方法不能被override,不能被重写。 
     3、final用在变量上,表明当前变量是一个终态的变量,是一个常量,这个变量的值一但被赋值后就不能被改变了。 
     对于final类型的成员变量的初始化方式: 
     1、声明变量时直接赋值 
     2、在构造方法中完成赋值,如果一个类有多个构造方法,就要保证在每个构造方法中都要完成对该final类型变量的初始化工作。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  class  FinalOriginalTest {   
     private  static  final  int ;   
     private  String name;   
     public  FinalOriginalTest(){   
         a =  3 ; //编译不通过
      }   
     public  FinalOriginalTest(String name){   
         this .name = name;
         a =  2 ; //编译不通过
      }   
     public  static  void  main(String[] args){   
          FinalOriginalTest ft =  new  FinalOriginalTest();
          FinalOriginalTest ft1 =  new  FinalOriginalTest( "hello" );
          
          System.out.println(ft.a); 
          System.out.println(ft1.a); 
      }   
}

<二>问题2上面的程序中,修正问题1之后,将private final int a;改为private static final int a;能否编译通过?如果不能,请说明理由.


解答:不能编译通过,因为a是静态变量,随着类的加载而加载,在这个类还没有实例化的时候,它的值就已经有了;

所以对于一个int类型的static final类型的变量a来说,我们只能在声明的时候就给它赋值private static final int a = 3;

然后把构造方法里面的赋值给注释掉,这样编译就能通过了。

还可以在static{}这样的静态块中初始化它。


<三>总结:

对于final类型的变量,

对于不加static我们可以有两种方式给它赋值:

声明变量时直接赋值;

在构造方法中完成赋值,

如果一个类有多个构造方法,就要保证在每个构造方法中都要完成对该final类型变量的初始化工作。

对于一个变量既是final又是static的,我们必须在声明变量时直接赋值。





       4,final修饰形数时
-------------这个地方会涉及到java中参数传递类型的问题!
应该说java中的传递只有值传递: ------------见CSDN/kkopopo播客
下面进入正题,分析如下代码:
问题:final在修饰形参时,为何传递进去的参数没被final修饰也可以?
1
2
3
4
5
6
7
8
9
10
11
public  class  Test4 {
     public  static  void  main(String[] args) {
         //static int age = 23;//static不能修饰局部变量
         int  age =  23 ;
         add(age, new  Person());
         //方法在传参数时,并没有要求穿进去的参数是final
     }
     public  static  void  add( final  int  x, final  Person p){
         //代码
     }
}
回答上面问题时先解决2个问题:
1,为啥用参数要用final修饰?
在JAVA中用final来修饰方法参数的原因是防止方法参数在调用时被篡改
2,何时用到了final修饰的参数?
匿名内部类中用局部变量时必须是final修饰,至于为什么,参考内部类的实现机制


那么对于用final来修饰方法参数的原因是防止方法参数在调用时被篡改这个原因,
理解起来可能会有歧义,
有的人认为是调用语句的地方的变量的实际值不会被修改,
另一种理解就是仅在调用方法内部不能被修改。

1,实际上第一种理解是有错误的:
(一)对于基本类型和String来说在调用的地方,用不用final来修饰都是一样的效果,如下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  class  Test4 {
     public  static  void  main(String[] args) {
         int  x =  1 ;
         String s =  "abc" ;
         System.out.println(x); //结果是 1
         System.out.println(s); //"abc"
         changeInteger(x,s);
         System.out.println(x); //1
         System.out.println(s); //abc
     }
     public  static  void  changeInteger( int  x,String str){
         x =  3 ;
         str =  "xyz" ;
     }
}
你把changeInteger( )方法中的参数设成final和非final的效果对调用的地方来说是一样子的。
(二)对于引用类型来说也是一样子的,用不用修饰都不会改变引用地址,而都可以改变引用变量的属性值
1
2
3
4
5
6
7
8
9
10
11
12
public  class  Test4 {
     public  static  void  main(String[] args) {
         Person person =  new  Person( "ljs" , 23 );
         System.out.println(person.getAge()+ ":" +person.getName()); //23:ljs
         change(person);
         System.out.println(person.getAge()+ ":" +person.getName()); //100:change
     }
     public  static  void  change( final  Person p){
        // person = new Person("ljs",23);//这句就是非法的,因为
         p.setAge( 100 );
         p.setName( "change" );
     }
}
先看程序运行过程:
person对象创建后,调用change( )方法,则final Person p也指向了 new   Person( "ljs" , 23 ),
如果此时在change( )中对p 赋值其他对象的引用地址,则编译失败,
因为,final只对引用的”值”(也即它所指向的那个对象的内存地址)有效,
它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。
至于它所指向的对象的变化,final是不负责的。
这很类似==操作符:==操作符只负责引用的”值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的
你把change( )方法中的参数设成final和非final的效果一样。

<二>另一种理解就是仅在调用方法内部不能被修改。 
对于第二种说法,是这个样子的,
我给了这个参数,你只能用这个参数的值,你不能修改它,对于基本类型和引用类型是一样的,
如下
// 如果不是 final  的话,我可以在 checkInt 方法内部把 i 的值改变(有意或无意的,
      // 虽然不会改变实际调用处的值),特别是无意的,可能会引用一些难以发现的 BUG
       public  static  void   checkInt( int   i)  {
            i = 200;// 这样是可以的,不会编译出错的
             //do something
      }
 
      // 如果是 final  的话,我可以在 checkInt 方法内部就没办法把 i 的值改变
      // 可以完全避免上面的问题
       public  static  void   checkInt( final  int    i)  {
            i = 200;// 这样是不可以的,会编译出错的
             //do something
      }
 
      //final  的引用类型方法参数
       publicstaticvoid  checkLoginInfo( final  LoginInfo login)
      {
            login =  new  LoginInfo();//error, 编译不过去
             //do something
      }
      // final 的引用类型方法参数
       publicstaticvoid  checkLoginInfo(LoginInfo login)
      {
            // 没有任何问题,但是肯定不符合此参数存在的初衷
            login =  new  LoginInfo();
             //do something
      }
>>>>
>>>
>>
>
最后的最后:
理解final问题有很重要的含义。

许多程序漏洞都基于此—-final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。

在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。

一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它”永远不变”。其实那是徒劳的.

final还有一个值得注意的地方,
1
2
3
4
5
6
7
8
class  Person{
     final  int  age;
     String name;
     public  void  doSomething(){
         System.out.println(age); //编译不通过,因为没有构造对final类变量进行初始化
         System.out.println(name); //编译通过
     }
}
对于类中的 成员变量,java虚拟机会自动进行初始化。
如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。
但是对于用final修饰的类成员变量,虚拟机不会为其赋予初值,必须在constructor(构造器)结束之前被赋予一个明确的值。

可以修改为”final int i = 0;”。
 
    



7.抽象类abstract
/*
  * 当多个类中出现相同功能,但是功能主体不同,
  * 这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。
  * 抽象类的特点:
  * 1,抽象方法一定在抽象类中。
  * 2,抽象方法和抽象类都必须被abstract关键字修饰。
  * 3, 抽象类不可以用new创建对象。因为调用抽象方法没意义。
  * 4,抽象类中的抽象方法要被使用,必须由子类复写起所有的抽象方法后,建立子类对象调用。
     如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类
abstract  class  Student1{
     abstract  void  study(); //没有{},即没有功能主体
     abstract  void  work();
}
abstract  class  baseStudent  extends  Student1{ //没有复写所有抽象方法
     void  study(){} //有功能主题,虽然是空
}
class  proStudent  extends  baseStudent{
     void  study(){}
     void  work(){}

public  class  AbstractDemo {
     public  static  void  main(String[] args) {
//      new Student();//
     }
}

特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。?????????????????????????????????????




8abstract练习
/*
假如我们在开发一个系统时需要对员工进行建模,员工包含 3 个属性:
姓名、工号以及工资。经理也是员工,除了含有员工的属性外,另为还有一个
奖金属性。请使用继承的思想设计出员工类和经理类。要求类中提供必要的方
法进行属性访问。
员工类:name id pay
经理类:继承了员工,并有自己特有的bonus。
*/
abstract  class  Employee {
     private  String name;
     private  String id;
     private  double  pay;
 
     Employee(String name,String id, double  pay) {
         this .name = name;
         this .id = id;
         this .pay = pay;
     }
     
     public  abstract  void  work();
 
}
 
 
 
class  Manager  extends  Employee {
     private  int  bonus;
     Manager(String name,String id, double  pay, int  bonus) {
         super (name,id,pay);
         this .bonus = bonus;
     }
     public  void  work() {
         System.out.println( "manager work" );
     }
}
 
class  Pro  extends  Employee {
     Pro(String name,String id, double  pay) {
         super (name,id,pay);
     }
     public  void  work() {
         System.out.println( "pro work" );
     }
}
class  MyTest {
     public  static  void  main(String[] args)  {
         System.out.println( "Hello World!" );
     }
}




9.模板方法模式
什么是模版方法呢?
在定义功能时,功能的一部分是确定的,但是有一部分是不确定,而确定的部分在使用不确定的部分,
那么这时就将不确定的部分暴露出去。由该类的子类去完成。
/*
需求:获取一段程序运行的时间。
原理:获取程序开始和结束的时间并相减即可。
获取时间:System.currentTimeMillis();
当代码完成优化后,就可以解决这类问题。
这种方式,模版方法设计模式。

*/
abstract  class  GetTime{
     public final void getTime(){//方法被final修饰不能被重写
         long  start = System.currentTimeMillis();
         //中间是测试代码块,无功能主体
         runCode();
         long  end = System.currentTimeMillis();
         System.out.println( "运行时间=" +(end-start));
     }
     public  abstract  void  runCode();
}
 
class  SubTime  extends  GetTime{
     public  void   runCode(){ //只重写这个
         //测试的代码块
     }
}
 
public  class  TemplateDemo {
     public  static  void  main(String[] args){
         SubTime gt =  new  SubTime();
         gt.getTime();
     }
}




10.接口interface
接口直接可以多继承
class用于定义类
interface 用于定义接口。
 
接口定义时,格式特点:
1,接口中常见定义:常量,抽象方法。

2,接口中的成员都有固定修饰符。
    常量:public static final
    方法:public abstract 
记住:接口中的成员都是public的。
 
接口:是不可以创建对象的,因为有抽象方法。
需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。
否则子类是一个抽象类。 
接口可以被类多实现,也是对多继承不支持的转换形式。java支持多实现。

**************************************************************************
面试题:---------> 为什么java接口中定义的变量都是静态常量?
解析:
1.首先明白什么是接口?
接口可以理解为特殊的抽象类,
当抽象类中的所有方法都是抽象方法时,
该类就可以通过接口的形式表达。

2,变量没有定义为final那么会出现什么后果?
如果接口A中有一个public访问权限的静态变量int x = 1,按照java的语义,我们可以不通过实现接口的对象来访问x,即A.x =3; 这样就改变了接口中的x的值。
正如抽象类中是可以这样做的,那么实现接口A的所有对象象都会自动拥有这一改变后的x值。也就是说一个地方改变了a,所有这些对象中a的值也都跟着变了。
这和抽象类有什么区别呢,怎么体现接口 更高的抽象级别呢,怎么体现 接口提供的统一的协议呢,那还要接口这种抽象来做什么呢?
所以接口中不能出现变量,如果有变量,就和接口提供的统一的抽象这种思想是抵触的
abstract  class  A{
     public  static  int  x = 1 ; //没有定义为final
     public void test(){
        A.x = 5;
    }
}
class  extends  A{
}
class  extends  A{
}
public  class  Test {
     public  static  void  main(String[] args){
         B b =  new  B();
         C c =  new  C();
         System.out.println(c.x); //结果5
         System.out.println(b.x); //结果5
     }
}
3,承接2所说
classB继承抽象类A,classC继承抽象类A,2个类的实例对象访问改变x 
Class b = newClassB(); 
b.x = 6;
记过将改变BC类的所有实例对象的x属性。

        a
abstract  class  A{
     public  static  int  i = 1 ;
}
class  extends  A{
}
class  extends  A{
}
public  class  Test {
     public  static  void  main(String[] args){
         B b =  new  B();
         C c =  new  C();
         b.x = 6;
         System.out.println(c.x);//结果6
         System.out.println(b.x);//结果6  
     }
}



11,抽象类和接口中是否有构造函数??***

抽象类一定有构造函数:
1、抽象类必须要能够被继承才有意义 
2、子类对象在构造时,必须要先调用父类的构造器 ,你不写编译器会给你加上,子类的构造里有super( );
所以说必须有构造函数

1
2
3
4
5
6
7
8
9
abstract  class  MyA{
     abstract  void  show();
}
class  MyB  extends  MyA{
     MyB(){
         super (); //这句话证明了抽象父类有构造函数
     }
     public  void  show(){}
}
1
2
3
4
5
6
7
8
9
10
abstract  class  MyA{
     abstract  void  show();
     abstract  void  test(); //子类没有实现该方法的状态下,子类也是抽象类,显然子类有构造函数
}
abstract  class  MyB  extends  MyA{
     MyB(){
         super (); //
     }
     public  void  show(){}
}
接口中不可以有构造函数:
1, 类可以实现多接口,那么对象在调用构造函数时,调用次序如何解决?所以不能有!
2,构造方法也是一种方法,是一种特殊的方法,接口中要求所有的方法必须是抽象方法,而构造方法有方法体


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值