Java学习笔记 面向对象(下)

1.this与super

  • this

    • this.域变量和this.成员方法
      明确表示用的是类的域变量

      public class Test{
      	 int i;
      	 public otherMethod(){
      		int i = 3;
      		this.i = i+2;
      	 }
      }
      
    • this(参数)
      引用重载的构造方法

    • this指代当前对象

      /*已设定class FighterPlane,其中有成员方法public void setA(A _a)
      和域变量private A a*/
      public void setA(A _a){
      	if(_a!=null)
      		{return a;}
      	else
      		return null;
      }
      class A{
      	FighterPlane fp;
      	public A(FighterPlane fpp){
      		this.fp = fpp;//this获得了FighterPlane对象的引用
      		fpp.setA(this);//this指代当前的A对象
      	}
      }
      //主程序main中有以下语句
      public static void main(String[] args){
      	FighterPlane ftp = new FighterPlane()//构造方法初始化
      	A a = new A(ftp);
      	/*在class A的构造方法中,this指代a,
      	并利用fpp.setA(this)将A对象传入fpp对象中,
      	实现两个对象在其内部属性中都有对方的引用(两个类互相关联)*/
      }
      
  • super
    super 指代父类中的域变量或方法;当子类的域变量名和方法名与父类的相同时,调用父类的同名域变量或方法要用super

    • super.域变量和super.成员方法
    • super(参数)
      ATTENTION: this可以指代当前对象,super不可以指代父类对象
    class A{
    	int x = 4;int y = 1;
     	public void Printme(){
        System.out.println("x="+x+"y="+y);
        System.out.println("class name: "+getClass().getName());
    	}
    }
    public class AA extends A{
    	 int x;
      /* 子类和父类定义了同名域变量,子类继承了父类的x,又定义了x,
    	 在生成子类对象时,子类对象将父类定义的同名域变量隐藏,
    	 在子类所用的x均为子类自己定义的,
    	 若使用父类定义的x,需采用super.x的形式     */
    	 
    	 public void Printme(){
       	 int z = super.x + 6;
         super.x = 5;
         super.Printme();//将父类中被覆盖的方法保留
         System.out.println("I am an "+getClass().getName());
         x = 6;
         System.out.println("z="+z+"x="+x+"super.x="+super.x="+super.x+"y="+y+"super.y="+y);
    }
    	public static void main(String arg[]) {
       		 int k;
        	A p1 = new A();
        	AA p2 = new AA();
       		p1.Printme();
        	p2.Printme();
    	 }
    }
    
    /*运行结果如下
    x=4 y=1
    class name:A
    x=5 y=1
    class name:AA
    I am an AA
    z=10 x=6 super.x=5 y=1 super.y=1   */
    

2.构造方法的多态

  • 构造方法的重载

    主要指构造方法可以被重载,一个类的若干重载的构造方法之间可以相互调用,需要用this关键字,同时这个调用语句应为整个构造方法的第一个可执行语句

    class AddClass{
    	 public int x = 0,y = 0,z = 0;
    	AddClass(int x){
    		this.x = x;
    	}
    	AddClass(int x,int y){
        	this(x);
        	this.y = y;
    	}
     	AddClass(int x,int y,int z){
       		this(x,y);this.z = z;
    	}
    	public int add(){
        	return x + y + z;
    	}
    }
    public class RunAddClass{
    	public static void main(String args[]){
       	 	AddClass p1 = new AddClass(2,3,5);
        	AddClass p2 = new AddClass(10,20);
        	AddClass p3 = new AddClass(1);
        	System.out.println("x+y+z="+p1.add());
        	System.out.println("x+y="+p2.add());
        	System.out.println("x="+p3.add());
    	}
    }
    /*运行结果如下
    x+y+z=10
    x+y=30
    x=1      */
    
  • 构造方法的继承调用
    子类可以调用父类的构造方法,调用时使用关键字super
    用法如下:

    • 调用父类构造方法不可以直接使用其名
    • 若父类构造方法含参数,子类在自己构造方法中调用时,需使用super并将调用语句作为子类构造方法的第一个可执行语句
    • 子类中没有显示地用super调用父类的构造方法,也没有用this重载构造方法时,默认调用父类无参数构造方法
    public class SonAddClass extends AddClass{
    	int a = 0,b = 0,c = 0;
    	SonAddClass(int x){
        	super(x);//调用父类构造方法不可以直接使用其名
        	a = x+7;
    	}	
    	SonAddClass(int x,int y){
        	super(x,y);//构造方法含参数,super放在构造方法的第一条代码
        	a = x+5;b = y+5;
    	}
    	SonAddClass(int x,int y,int z){
        	super(x,y,z);
        	a = x+4;b = y+4;c = z+4;
    	}
    	public int add(){
        	System.out.println("super:x+y+z="+super.add());
        	return a+b+c;
    	}
    }
    public class RunAddClass{
    	public static void main(String args[]){
       		AddClass p1 = new SonAddClass(2,3,5);
        	AddClass p2 = new SonAddClass(10,20);
        	AddClass p3 = new SonAddClass(1);
        	System.out.println("a+b+c="+p1.add());
        	System.out.println("a+b="+p2.add());
        	System.out.println("a="+p3.add());
    	}
    }
    /*运行结果如下:
    super:x+y+z=10
    a+b+c=22
    super:x+y+z=20
    a+b=40
    super:x+y+z==1
    a = 8   */
    

Tips: 作为对象属性时,各种数据的初值(类的静态属性也依据下标赋初值)

成员变量初值
byte,shprt,int0
long0L
float0.0F
double0.0D
char‘\u0000’
booleanfalse
String等引用声明null

ATTENTION:

  • 类域变量不赋初值在编译时不提示,成员方法中的变量不赋初值会提示
  • 子类中调用this或super语句,不同时存在,位于第一句

3.抽象类

1) 抽象类中可以有0个或多个抽象方法,也可以有非抽象方法
2)有抽象方法必须用abstract修饰,不出现的时候可用可不用

3)抽象类不可以创建对象,创建对象由派生类的具体子类(继承)实现
4)抽象类可以有自己的声明,声明可以引用所有具体子类的对象,但只能使用子类从抽象类中继承过来的方法

5)抽象方法在抽象类中只指定其方法名,不写实现代码
6)抽象类中,非抽象方法(已实现的方法)可以调用抽象方法

7)抽象类必派生子类,若子类为具体子类则必须实现抽象类中的定义的所有抽象方法,若子类仍未抽象类,则子类抽象方法不可与父类抽象方法同名

8)abstract不可与final并列修饰同一个类(final最终类不可被继承)

4.接口(interface)

#接口可以指能被引用调用的方法,也可以指interface这个专有概念

  • 接口的格式:

    [public] interface 接口名[extend父接口名列表]
    {
    	//静态常量数据成员声明
    	[public][static][final]域类型 域名 = 常量值  
    	//抽象方法声明
    	[public][abstract]返回值 方法名(参数列表)[throw 异常列表] 
    }
    

    1)interface前修饰符只能为public或默认
    2)接口中定义的数据成员全是静态常量,不写修饰符也默认为final static(表明没有对象),没有域变量
    3)接口无构造方法,所有成员方法均为抽象方法,不写修饰符也默认为abstract,因此方法前不能用final修饰
    4)接口可以通过extends关键字继承父接口
    5)接口和其子类不可以有自己的实力对象,可以有自己的声明,可以引用实现接口或子接口的类对象

  • 接口的实现
    1)用implements实现,实现接口的类前修饰符必须使用public
    2)一个类实现多个接口时,implements后用逗号隔开多个接口的名字,一个接口也可以由多个类实现
    3)实现接口的类继承接口中定义的常量
    使用常量:“类名.常量” 、“引用名.常量”、“接口名.常量”
    4)实现接口的不是抽象类时,则必须实现接口中所有的抽象方法,抽象类可以不全部实现,但其具体子类必须将接口中剩余抽象方法实现完

例一:用接口和继承实现计算正方形对角线
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Math;
//定义接口
interface function{
    public double calculate();//默认为抽象方法
}
//接口实现
class rectangle implements function{
    public int a;public int b;
    public rectangle(int A,int B){
        a = A;
        b = B;
    }
    public double calculate(){ //实现接口中的抽象方法
        return (Math.pow(Math.pow(a,2)+Math.pow(b,2),0.5));
    }
    
}
class square extends rectangle{
    int a;
    public square(int n) {
        super(n,n);   //调用父类构造方法
        a = n;
    }
    public double calculate(){
        return (Math.pow(Math.pow(a,2)+Math.pow(a,2),0.5));
    }
    double area()
    { return Math.pow(a,2); }
}
public class lenth {
    public static void main(String args[])throws IOException {
        BufferedReader din = new BufferedReader(new InputStreamReader(System.in));
        int n;double l;
        n = Integer.parseInt(din.readLine());//前三行为了实现输入数组
        //使用接口
        function f = new square(n);
        l = f.calculate();
        System.out.print(l);
    }
}

ATTENTION: 不可以通过接口调用实现接口的类独有的方法,如f.area(),会报错

5.引用

  • 声明可以引用所有具体子类的对象,但只能使用子类从抽象类中继承过来的方法

  • 引用替换规则:父类声明可以引用所有具体子类对象,并可以被替换,子类声明不可以引用平行级别的其他类对象,也不可引用父类对象

  • 父类声明和子类声明引用同一个子类对象的区别:父类可以通过显示的转化赋值给子类声明,子类声明引用的子类对象赋值给父类不需要显示的转化

    //A是父类,A1,A2是子类
    A a  = new A();    //a是A的引用对象
    a = new A1();      //a又引用A1对象,引用被替换
    A1 a1 = (A1)a;     //显示转化,a1通过a也引用了A1对象
    a = new A2();      //a放弃引用A1,引用A2对象
    A aa = a1;         /*子类A1对象通过a1赋值给父类声明aa,aa也引用了A1对象
    			     	但只可以引用A1中从A继承来的属性和方法 */
    

    WRONG:

    A1 a1 = new A2();    //错误,平行级间不可以相互引用
    A1 a1 = new A();     //错误,子类不可引用父类
    
  • 引用比较

    • equals 方法(是object类的方法),比较两个引用是否相同

      if(p1.equals(pp))    //参数为对象
      {System.out.println("p1和pp引用相同")}
      

      但String类,包装类、URL类会将其覆盖,虽然参数传入对象引用,但比较他们的值

    • 使用 “= =” 比较
      1)若两边为对象引用,则比较引用是否相同
      2)若两边是数值,则比较数值是否相同
      3)若两边是数值且类型不同,可能发生类型转换(10==10.0返回true)
      4)若一边是引用一边是值,编译错误

    • instanceof比较引用类型
      a(对象引用) instanceof A(类);
      1)若a是A或A的子类的实例,返回true
      2)若a是A父类的实例,返回false
      3)若a和A无关系,编译错误

6.类的其他相关内容

  • 内部类
    即在某个类内部又定义了一个类,被内部类嵌入的叫外部类

    1)内部类定义在外部类域的位置,可以被private、protect等修饰符修饰,若定义在方法内则不可被任何修饰符修饰,并只能访问方法体内的常量,不可访问方法体内的局部变量
    2)内部类可以访问外部类所有属性,包括private修饰的属性和方法;外部类不可以直接访问内部类中的成员
    3)内部类前修饰符不为public时,需从外部类的特定接口方法来得到内部类,如上述代码中的 getInner();
    4)static修饰内部类时,内部类相当于外部类的一个静态属性,内部类中可以有static属性或方法,static内部类不可以使用外部类中非静态的属性或方法;产生对象的方式如下

    Outer.Inner inner = new Outer.Inner();
    

    5)非静态的内部类如果不是定义在静态方法中,就不能在类的静态方法中调用
    6)内部类也可以是抽象类

    //public class Inner 嵌在class Outer中
    //Outer 中有一个方法是Inner getInner(){return new Inner();}
    
    public static void main(String[] args){
    	Outer outer = new Outer();   //先产生外部类对象
    	//Outer.Inner inner = outer.getInner();     
    	//若内部类前没有public,用这种方式访问
    	Outer.Inner inner = outer.new Inner();      
    	//利用外部类对象引用产生内部类实例
    }
    
  • 匿名

    • 匿名对象
      在对象创建时没有显示地为其指定引用的对象
      匿名对象方法调用方式为直接调用而不是通过引用

      //原始
      Person p1 = new Person();
      p1.shout();
      //匿名对象
      new Person.shout();
      

      使用条件:
      1)对一个对象只需要进行一次方法调用
      2)将匿名对象作为参数传递给一个方法
      (如下面第三组代码中 new Outer().callInner(new inner()); )

    • 匿名内部类

      • 用法
        1)匿名内部类不能有构造方法
        2)若匿名内部类继承了一个只含有参数的构造方法的父类,则创建它的对象时,在括号中必须带上这些参数
        3)匿名内部类不能定义静态成员和方法
        4)匿名内部类不能被public、protect、private、static修饰
        5)只能创建匿名内部类的一个实例

      • 格式:

      new 类 A (){
      	方法体
      }//此时产生的是类A的子类对象
      或
      new 接口 A(){
      	方法类
      }//此时产生的是接口A的实现类对象
      
      • 使用条件
        1)只用到类的一个实例
        2)类在定义以后马上用到
        3)类很小(4行代码以下)
      public class Anonymity{
      	public static void main(String[] args){
      		Object obj = new Object(){
      			//定义了一个匿名类,匿名类覆盖了object类的hashcode方法
      			public int hashcode(){
      				return 42;
      			}
      		}
      		System.out.println(obj.hashcode());
      	}
      }
      
      abstract class Anonymity{
      	abstract public void fun1();
      };
      public class Outer{
      	public static void main(String[] args){
      		//产生抽象类的匿名具体子类对象,并非“抽象类可以实例化”
      		new Outer().callInner(new Anonymity(){
          		public void fun1(Anonymity(){
                 		System.out.println("匿名类测试")
          		}
      		});
      	}
      	public void callInner(Anonymity a){
      	a.fun1();
      	}
      }
      //若改为有名内部类:
      abstract class Anonymity{
      	abstract public void fun1();
      };
      public class Outer{
      	public void callInner(Anonymity a){
      		a.fun1();
      	}
      	public static void main(String[] args){
      		class inner extends Anonymity{
          		public void fun1(Anonymity(){
              		System.out.println("匿名类测试")
          		}
      		};
      		new Outer().callInner(new inner());
      	}
      }