疯狂java第五章——面向对象(上)

 

一、类和对象

定义类

java的类名由一个或者多个有意义的单词组合而成,每个单词的首字母大写,其他的全部消协,并且单词之间没有分隔符。

定义类:[修饰符] class 类名
	{
        //成员变量(field)
       //方法(method)
       //构造器(constructor)
}———类体

成员变量:用于定义该类或者实例的所包含的状态数据。

         语法格式: [修饰符]  类型 成员变量名 [= 默认值];

      —修饰符:可以省略 public | protected | private、static、final、(transient:序列化相关)

      —类型: 可以是8个基本类型,可以任意的引用类型(数组、String、JDK任何类、自己定义的类)

      —成员变量名:从语法角度来看, 变量名只要是一个合法的标识符即可。

             从程序可读性(习惯)来要求, 变量名应该是一个或多个有意义的单词连缀而成,中间不要分隔符,

             第一个单词的首字母小写,后面每个单词首字母大写 —— 驼峰写法。

             常量全部单词大写,单词之间用_隔开。

方法:用于定义该类或者实例所包含的功能实现和行为特征。

[修饰符]  返回值类型  方法名(形参列表)  —— 这行叫方法签名。

     {
     //方法体:定义变量(包括数组)、变量赋值、流程控制、数据结构
     }

     正常的方法 = 方法签名 + 方法体

    —修饰符:  可以省略public | protected | private 、static、final | abstract

       —返回值类型:可以是8个基本类型,可以是任意的引用类型(数组、String、JDK任何类、自己定义的类)

          该返回值类型,指定了调用该方法将会得到一个Xxx类型的值,表明我们的程序可以从调用方法中得到xxx类型的值。

          如果调用方法后,不会得到任何的东西,就将该方法定义为没有返回值,用void来声明返回值类型。

          如果你声明了返回值类型,方法体内必须用有效return 语句来返回【返回值】

    —方法名:从语法角度来看,方法名只要是一个合法的标识符即可。

             从程序可读性(习惯)来要求,方法名应该用驼峰写法。

             建议:方法一般用于描述类的行为,因此方法名通常使用动词,只定义项目中感兴趣的。

—形参列表:形参类型1,形参类型2,形参类型3,。。。。。

           每个形参都满足”形参类型 形参名”的格式;多个形参之间用逗号隔开

构造器:用于构造该类的实例。

【如果你没有为类写构造器,系统会默认为该类提供一个无参数的构造器】

     构造器定义、与方法定义有点相似。主要体现为2个区别:

        1. 构造器定义无需声明返回值类型,返回值类型总是当前类,不需要显式使用return。

        2 . 构造器的名字必须与类名相同。

 [修饰符]  构造器名 (形参列表)
     {
    //代码:定义变量(包括数组)、变量赋值、流程控制、数据结构

    }——构造器体

    ——修饰符 -  可以省略 public  | protected | private

    ——构造器名 -  必须与类名相同。

    —— 构造器体 - 与方法体类似,都是放各种执行性的代码。

static关键字
static修饰的成员表示他是属于类的,类变量类方法。也叫静态。静态成员不能直接访问非静态成员。
static真正的作用是用来区分成员变量,方法,内部类,初始化块属于类本身还是属于实例。有static修饰的属于类本身,没有static修饰的成员数与类的实例

 

 可做如下事情:

   1.定义变量 ,

                 所有类都属于引用类型,都可用于声明变量

   2.调用static修饰方法或static修饰的变量

   3.创建对象

              new 构造器(参数)

  4.派生子类

 实例可做如下事情:

     - 可调用的方法 ——驱动它去完成特定行为。

     - 访问field —— 获取它的状态。

二、对象

对象可做如下事情:

      1.调用无static修饰的成员变量

      2.调用无stastic修饰的方法

 

对象的产生和使用

Person p=new Person();//定义p变量,并为p变量赋值,person是对象
p.say("你真棒");
p.name(“李刚”);//直接给成员变量赋值

类定义的是多个实例的特征,因此类不是一种具体存在,实例才是具体存在

对象和引用

堆里面的对象可以有多个引用。
Person p1=p;
这时p1和p是引用变量都指向同一个对象。
如果希望通知垃圾回收机制回收某个对象,把引用变量赋值为null

this

 this 关键字总是代表调用该方法的对象.

        - 1.如果在构造器中,this代表该构造器正在初始化的对象。

        - 2.如果在方法中, this代表调用该方法的对象。

      在方法中,谁调用该方法,this就代表谁。

      在构造器中,构造器正在初始化谁,this就代表谁。

      无论是构造器、还是方法,它们中出现的this引用,只有等到调用该构造器、调用该方法时,才能确定代表谁

      如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀

public class Dog {
    public void jump()
    {
        System.out.println("正在执行jump方法");
    }
    public void run()
    {
        //如果想在这个方法里面调用另一个方法
        //如果没有this,则必须再创建一个对象
        //有了this,就不需要创建了,因为this就是指向调用该方法的对象
        this.jump();
        jump();
    }

 【注:】 this 不能用在有static 修饰的方法中.static修饰的方法属于类,不属于对象

static修饰的方法,是用类来直接调用的,因此不需要再创建一个对象,因此也就不使用this,因此static对象不能访问非静态成员

public class StaticAccessNonStatic {
    
    public void info()
    {
        System.out.println("简单的方法");
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new StaticAccessNonStatic().info();

    }

}

大部分时候,普通方法访问其他方法,成员变量无需使用this,但是如果方法里面有局部变量和成员变量同名,但是又需要在方法里面访问,则必须适应this
this也可以用于构造器作为默认引用。代表构造器正在初始化的对象。

public class ThisInConstructor {
    //定义一个名为foo的成员变量
    public int foo;
    public ThisInConstructor ()
    {
        // 在构造器中又定义了一个foo
        //this代表正在初始化的对象,把他的foo成员变量设为6
        int foo=0;
        this.foo=6;
    }

this还可以作为普通方法的返回值,表示返回调用该方法的对象

三、方法

 

方法的所属性

  ★  一旦将一个方法定义在一个类里,如果用static修饰了,这个方法属于该类本身,否则属于这个类的实例.

           有static修饰,属于类;没有static修饰,属于对象。

       ★ 方法不能独立执行,必须要有调用者.(如:类.方法、对象.方法)

           方法必须由属主调用。谁的方法,谁调用。

           没有static的方法,属于对象,由对象调用。

             有static的方法,属于类,由类调用。

       ★ 方法不能独立定义,只能定义在类里.

           方法要么属于一个类(有static),要么属于一个对象(无static)

  调用方法时,总是需要主调(主语调用者),如果方法有static修饰,要用类做主调。

                                              如果方法没有static修饰,要用对象做主调。

      在有些时候,可以省略主调——

          如果在没有static修饰的方法中调用另一个方法,默认可以省略this.前缀。

          如果在没static修饰的方法中调用另一个没static的方法,默认可以省略this.前缀。

          如果在没static修饰的方法中调用另一个有static方法,默认可以省略 类名.前缀。

          如果在有static修饰的方法中调用另一个有static方法,默认可以省略 类名.前缀。

          如果在有static修饰的方法中调用另一个没static方法——这种调用是不允许的。

方法的参数传递机制

参数传递方式是一种值传递方式。也就是实参的副本保存在形参中。如果形参是基本类型,那么方法中对形参的修改不会改变实参;如果形参是引用类型,仍然是指传递,由于引用类型是指针,保存的是地址,所以方法中对形参的修改会改变实参的值,但是如果在方法中把形参的引用赋值为null,则实参不会改变。

class DataWrap
{
	int a;
	int b;
}
public class ReferenceTransferTest
{
	public static void swap(DataWrap dw)
	{
		// 下面三行代码实现dw的a、b两个成员变量的值交换。
		// 定义一个临时变量来保存dw对象的a成员变量的值
		int tmp = dw.a;
		// 把dw对象的b成员变量值赋给a成员变量
		dw.a = dw.b;
		// 把临时变量tmp的值赋给dw对象的b成员变量
		dw.b = tmp;
		System.out.println("swap方法里,a成员变量的值是"
			+ dw.a + ";b成员变量的值是" + dw.b);
		// 把dw直接赋为null,让它不再指向任何有效地址。
		dw = null;
	}
	public static void main(String[] args)
	{
		DataWrap dw = new DataWrap();
		dw.a = 6;
		dw.b = 9;
		swap(dw);
		System.out.println("交换结束后,a成员变量的值是"
			+ dw.a + ";b成员变量的值是" + dw.b);
	}
}

运行结果:原本a=9,b=6,交换后a=9,b=6

参数个数可变的方法

在最后一个形参的类型后面加上"...", 表示形参可以接受多个参数。本质上就是一个数组类型的形参。传入的实参可以是多个参数,也可以是一个数组。
但是参数个数可变的形参必须处于形参列表的最后,且只能有一个。
如果形参是数组的话,传递的方式是
test(new String[]{"abs","cps"});
如果形参时参数可变的,传递方式除了上边的方式,还可以是
test("abs","cds");

public class Varargs {
    //定义一个参数可变的方法
    public static void test(int a,String ...books)
    {
        //books被当作数组处理
        for(String tmp:books)
        {
            System.out.println(tmp);
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        test(4,"ans","csd");
    }
}

 

public class Recursive {
    public static int fn(int n)
    {
        if(n==1)
        {
            return 1;
        }else if(n==2)
        {
            return 2;
        }
        else 
        {
            //方法中调用自身
            return 2*fn(n-1)+fn(n-2);
        }
        
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(fn(10));

    }

}

 递归方法

         所谓递归:就是指,在方法里再次调用自身。

         递归,相当于一个隐式的循环。递归,也要有一个结束点。

         比如有一个数列: f(1) = 2; f(2)=5;

                          f(n) = f(n+2) - 2 * f(n+1);

         计算f(10)的值。

         【注意:】递归一定要有结束点。

       可以考虑对    f(n) = f(n+2) - 2 * f(n+1) 公式进行变换。定义一个新的N, 让N = n + 2.

         公式可改为:   f(N-2) = f(N) - 2 * f(N-1)

                       f(N) = 2 * f(N-1) + f(N-2)

    

public class Recursive
{
	public static int fn(int n)
	{
		if (n == 0)
		{
			return 1;
		}
		else if (n == 1)
		{
			return 4;
		}
		else
		{
			// 方法中调用它自身,就是方法递归
			return 2 * fn(n - 1) + fn(n - 2);
		}
	}
	public static void main(String[] args)
	{
		// 输出fn(10)的结果
		System.out.println(fn(10));
	}
}

 方法的重载

“两同一不同”
同一个类中方法名相同,参数列表不同。
注意被重载的方法里面包含了参数可变的形参。

public class OverloadVarargs {
    public void test(String msg)
    {
        System.out.println("只有一个形参的test方法");
    }

    public void test(String ... books)
    {
        System.out.println("多个形参的test方法");
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        OverloadVarargs olV =new OverloadVarargs();
        olV.test("aa");
        olV.test("bb","cc","dd");
        olV.test();
        olV.test(new String[]{"aa"});

    }

}

  Java调用方法时,要确定一个方法,需要3个东西:

            1. 调用者

            2. 方法名。

            3. 参数列表。         

五、 成员变量和局部变量

                                      ↗  类变量(有static修饰),属于类,用于描述类本身的状态

                    成员变量

             ↗                      ↘  实例变量,属于各个实例,用于描述实例的状态

       变量

                                       ↗  方法局部变量: 在方法中声明的变量

             ↘          

                     局部变量  →  代码块的局部变量, 在代码块中声明的变量。

 

                                        ↘  形参

       成员变量:变量用于描述某个类、对象的固有信息

                        需要一个变量来保存该类或者实例运行的状态信息

                        需要变量在某个类的多个方法之间进行共享

       类变量,用描述类的状态;实例变量,用于描述实例的状态。

       如果要访问类变量(获取值, 也包括对它赋值),必须指定类作为主调。

       如果要访问实例变量(获取值, 也包括对它赋值),必须指定的对象作为主调。

 

      ★ 成员变量可以不指定初始值. 

         如果程序员没有为成员变量指定初始值,系统会为之分配默认的初始值,规则为,

         与动态初始化数组时,数组元素的初始化规则完全相同。       

         类变量,并不是属于对象,而是属于类本身,当系统初始化类时,就会为类变量分配内存、并指定初始值。

         垃圾语法:【Java允许通过对象来访问类变量、也可调用类方法】

         以后你们编程时,永远不准通过对象来访问类变量、也不准通过对象来调用类方法。应该用类名。

         但以后,如果你在面试、考试...遇到有人通过对象来访问类变量、通过对象来调用类方法,

         首先你先用把对象换成类名——这才是本质。

   成员变量的作用域:

               类变量:从类加载、并初始化开始有效, 直到该类被销毁时无效。

                             如果是官方JVM,JVM不退出,类信息就不会被销毁。

                            尽量少用static修饰的变量。

               实例变量:有多少个对象,就有多少个实例变量。每个对象都会持有自己的实例变量。

                               从对象加载、并初始化开始有效, 直到该对象被销毁时无效。

                               只要一个对象没有引用变量指向它,它就变成垃圾,垃圾回收器将会在接下来时间里回收它。              

 ★ 局部变量

        

        1. 局部变量必须由于程序员显式赋值。

           如果程序员没有赋值,该局部变量不能被使用。

        2. 局部变量的作用域很短, 所有局部变量都保存在各自的方法栈区中。

             形参:方法结束,就失效。

             方法局部变量:从声明它开始有效,方法结束,就失效。

             代码块局部变量:从声明它开始有效,代码块结束,就失效。

     总则:优先使用作用域更小的变量,好处:

           1. 节省内存。

           2. 更符合程序“高内聚、低耦合”的原则。

必须先给方法局部变量和代码块局部变量现实初始化,即指定初始值。否则不可以访问。
一个类不能有两个同名的成员变量,一个方法也不能有两个同名的方法局部变量。但是两个代码块里面的代码块局部变量可以同名。
如果先定义代码块局部变量,后定义方法局部变量。代码块和方法里面的局部变量也可以同名。
允许局部变量和成员变量同名。此时局部变量覆盖成员变量,如果想访问成员变量,要用this。

public class VariableOverrideTest
{
    // 定义一个name实例变量
    private String name = "李刚";
    // 定义一个price类变量
    private static double price = 78.0;
    // 主方法,程序的入口
    public static void main(String[] args)
    {
        // 方法里的局部变量,局部变量覆盖成员变量
        int price = 65;
        // 直接访问price变量,将输出price局部变量的值:65
        System.out.println(price);
        // 使用类名作为price变量的限定,
        // 将输出price类变量的值:78.0
        System.out.println(VariableOverrideTest.price);
        // 运行info方法
        new VariableOverrideTest().info();
    }
    public void info()
    {
        // 方法里的局部变量,局部变量覆盖成员变量
        String name = "孙悟空";
        // 直接访问name变量,将输出name局部变量的值:"孙悟空"
        System.out.println(name);
        // 使用this来作为name变量的限定,
        // 将输出name实例变量的值:"李刚"
        System.out.println(this.name);
    }
}

成员变量的初始化和内存中的运行机制

类变量和成员变量都在堆中保存。只是不再同一个地方。成员变量属于实例,类变量属于类。

局部变量的初始化和内存中的运行机制

局部变量保存在栈中,不需要垃圾回收,占的内存很小。直到被付初值才会被分配内存

变量的使用规则 

成员变量的使用情形

  1. 某个类或者实例的固有信息。如果个个实例都是一样的,用类变量。如果不是一样的,用实例变量。
  2. 保存该类或者实例运行时候的状态信息。
  3. 多个方法都要用到。
  4. public class ScopeTest1
    {
    	// 定义一个类成员变量作为循环变量
    	static int i;
    	public static void main(String[] args)
    	{
    		for ( i = 0 ; i < 10 ; i++)
    		{
    			System.out.println("Hello");
    		}
    	}
    }
    

     

                             类变量      实例变量      方法局部变量    代码块的局部变量         形参

    需要指定初始值      ×              ×                        √                        √                由调用者负责传入值

    何时生效?        类初始化     对象初始化     声明该变量         声明该变量      整个方法

    何时销毁           类消亡          对象消亡             方法结束          代码块结束      方法结束

    占几块内存          1个        有多个对象       方法执行几次      方法执行几次     方法执行几次

                                              有多少实例变量   就有多少变量      就有多少变量     就有多少变量

 

 

六、 实现良好的封装

理解封装

通过访问控制符把该隐藏的隐藏起来,该暴露的暴露出来。

使用访问控制符

          private  : 类访问权限。

                      如果一个成员(field、方法、内部类、构造器)用private修饰,表明该成员只能在当前类中被访问。

                      【彻底隐藏】

         default     : 包访问权限。

                      如果一个成员(field、方法、内部类、构造器)不用访问控制符修饰,

                      表明该成员只能在当前类或当前包中被访问。

                      【部分隐藏】

          protected: 子类访问权限

                      如果一个成员(field、方法、内部类、构造器)用protected访问控制符修饰,

                      表明该成员可以在当前类、当前包、该类的子类中被访问

                      【部分暴露】

          public:    公开访问权限

                      如果一个成员(field、方法、内部类、构造器)用public访问控制符修饰,

                      表明该成员可以在任意地方被访问。

                      【彻底暴露】

关于访问控制符的使用

  1. 绝大部分成员变量都用private,只有少部分用static,类似全局变量的成员变量用public。除此之外,辅助实现该类的其他方法的工具类也用private。
  2. 如果某个类主要做其他类的父类,则用protected.
  3. 构造器用public。大部分外部类用public。

package和import

   Java建议,包名,用公司的 域名倒写.项目名 —— 包名应该是所有字母小写。

         要一个类加包,要做如下两件事情:

            - 在源代码中使用package定义包。

            - 把生成的class文件放在对应的文件结构下。

         如果一个类位于指定包下,那么该类的完整类名应该 包名+类名, 光写类名是错的。

          以后用该类时,就需要写包名+类名

          为了避免在源代码中总要写包名+类名,就可用import来导入包。

          import有两种格式:

            import java.util.*;    *只能代表类,用于表示导入指定包的所有类。

            import 包名.类名 —— 只导入一个类。

     Java源代码默认会 import java.lang.*;

package ,import

同一个包下的类可以自由访问,但是子包里面的还是必须要写全名

import可以导入包下面的全部类,子包单独import

,import static

  import static - 可以不写类名。

         import static可用于导入指定类的指定static Field,或static方法。

                     也可用于导入指定类的全部static修饰成员。

         import static语句中的*只能代表指定类的静态成员。      

       import的作用是在源代码中省略写包名。

       import static的作用是在源代码中省略写类名。

java 常用包

 

七、 构造器的作用和构造器重载

使用构造器执行初始化

 构造器的作用: 用于初始化对象。

                     必须通过new 构造器来调用它

        所有类,都有构造器!

       如果我们为类写了构造器,自然就有构造器。

          如果我们没有写构造器, 系统会自动提供一个无参数的构造器。

public class ConstructorTest
{
	public String name;
	public int count;
	// 提供自定义的构造器,该构造器包含两个参数
	public ConstructorTest(String name , int count)
	{
		// 构造器里的this代表它进行初始化的对象
		// 下面两行代码将传入的2个参数赋给this代表对象的name和count实例变量
		this.name = name;
		this.count = count;
	}
	public static void main(String[] args)
	{
		// 使用自定义的构造器来创建对象
		// 系统将会对该对象执行自定义的初始化
		ConstructorTest tc = new ConstructorTest("疯狂Java讲义" , 90000);
		// 输出ConstructorTest对象的name和count两个实例变量
		System.out.println(tc.name);
		System.out.println(tc.count);
	}
}

构造器重载

 方法重载: 2同1不同

        构造器重载与方法重载几乎是相似的:构造器名必然相同;形参列表不同。

通过this调用另一个重载的构造器

  this关键词有两个作用:

         1. this引用 —— 如果在构造器中,代表构造器正在初始化的对象。

                          如果在方法中,代表正在调用该方法的对象。

            this引用总是代表该类的实例。

         2. this调用:

             只能用在构造器、而且必须是构造器中第一条代码。

             this调用总是调用当前类中重载的构造器。this调用到底调用哪个构造器,取决于传入的参数值。

            this调用更好! 因此它可以直接复用已有的构造器。

                            1. 代码更简洁。 2. 更有利于以后维护。提高了可维护性。

 

八、继承

  面向对象的3大特征: 封装、继承、多态

继承的特点

 Java的继承是一种类与类之间关系,是一种“由一般到特殊”的关系。子类对象一种特殊的父类实例。

java是单继承,只能有一个直接父类,但是可以有多个间接父类。

eg:  Fruit extends Plant

       Apple extends Fruit

子类是父类的扩展。 当子类extends父类之后,就可以自动从父类那里“获取”如下东西(这就是为啥叫继承):

             1. field。

             2. 方法。

但是 子类不能获得父类的构造函数。

如果你没有显式指定extends ,默认是extends Object。    "所有类都是Object的子类"

 重写父类方法

如果子类对于继承下来的父类的方法不满意,可以重写这个方法。
‘两同两小一大原则’
“两同” 方法名相同,参数列表相同
“两小” 返回值类型要小,抛出的异常要小
“一大” 权限要大
重载和重写:是两个东西。重载是一个类的多个方法,由于形参列表不同所以不同;重写是子类又写了一个和父类对应的方法。

九、 super

 super有两个作用:

            A  super限定

                   强制指定访问从父类继承得到的field、调用从父类继承得到方法。

                   需要在子类方法中调用父类被覆盖的实例方法(不能出现static修饰的方法)

                   super.Field

                   super.方法名

                super限定的作用:

                   如果当前类中定义了与父类同名的field、重写了父类的方法

                   ——如果在该类中直接访问该field、调用方法,默认是访问当前类的field、调用当前类的方法。

class BaseClass
{
	public int a = 5;
}
public class SubClass extends BaseClass
{
	public int a = 7;
	public void accessOwner()
	{
		System.out.println(a);
	}
	public void accessBase()
	{
		// 通过super来限定访问从父类继承得到的a实例变量
		System.out.println(super.a);
	}
	public static void main(String[] args)
	{
		SubClass sc = new SubClass();
		sc.accessOwner(); // 输出7
		sc.accessBase(); // 输出5
	}
}

            B super调用

              super调用是子类调用父类的构造器。 到底调用哪个构造器,取决于传入的参数。

              this调用是一个构造器调用另一个重载的构造器。  到底调用哪个构造器,取决于传入的参数。

 

                  super调用只能在构造器用,而且必须位于第一行

              this调用与super调用不能同时出现。

              【规则:】 子类构造器【总】会调用父类的构造器。只调用一次!

                          1. 如果子类构造器中,没有使用super调用,子类构造器会自动调用父类无参数的构造器。

                          2. 如果子类构造器中有使用super调用,根据传入的参数显示调用父类对应的构造器。

              【注意】: 子类构造器总会调用父类的构造器。

                        当父类没有无参数的构造器时,子类的构造器中【必须显式】地用super调用父类有参数的构造器。

                              否则:子类构造器会自动(隐式)调用父类无参数的构造器——必然导致找不到构造器的错误

     总结: 无论你创建哪个对象,系统最先调用的总是Object类的无参数构造器。

注意:如果父类中的方法需要被外部类调用,用public修饰

      不希望子类重写该方法,用final修饰符

       希望父类的某个方法被子类重写,但不希望被其他类自有防问,用protected修饰

何时需要父类子类继承关系?

  1. 子类需要额外增加属性
  2. 子类需要增加自己独有的行为方式
class Creature
{
	public Creature()
	{
		System.out.println("Creature无参数的构造器");
	}
}
class Animal extends Creature
{
	public Animal(String name)
	{
		System.out.println("Animal带一个参数的构造器,"
			+ "该动物的name为" + name);
	}
	public Animal(String name , int age)
	{
		// 使用this调用同一个重载的构造器
		this(name);
		System.out.println("Animal带两个参数的构造器,"
			+ "其age为" + age);
	}
}
public class Wolf extends Animal
{
	public Wolf()
	{
		// 显式调用父类有两个参数的构造器
		super("灰太狼", 3);
		System.out.println("Wolf无参数的构造器");
	}
	public static void main(String[] args)
	{
		new Wolf();
	}
}

 

十、多态

多态性

  多态的定义:同一个类型的变量,调用同一个的方法(做同一件事情),呈现出多种行为,这就是多态。

       对于一个引用变量来说,可以认为有两个类型:

           1. 编译时类型。声明该变量时所用的类型,就是编译时类型。

               对于编译器来讲,编译器只管变量的编译时类型,与运行时类型无关。

           2. 运行时类型(实际类型)。该变量实际所指向的对象的类型,就是运行时类型。
相同类型的变量,调用同一个方法时呈现出多种不同的行为特征,这就是多态。
成员变量不具有多态性。

eg:Object p=new person//把子类对象直接赋给一个父类引用变量

编译时类型object ,运行时类型person

P只能调用object类的方法,而不能调用person

若需要它调用person 类的方法,则需要强制转换为运行时的类型

 引用变量的强制类型转换

如果想让引用变量调用它运行时的类型的方法,则需要强制类型转换。只能是具有继承关系的两个类型之间。如果是两个没有任何关系的,无法通过编译。如果父类实例转化成子类实例,则这个对象在运行时候必须是子类实例才行,否则会有异常。用instanceof处理。

public class ConversionTest
{
	public static void main(String[] args)
	{
		double d = 13.4;
		long l = (long)d;
		System.out.println(l);
		int in = 5;
		// 试图把一个数值类型的变量转换为boolean类型,下面代码编译出错
		// 编译时候会提示: 不可转换的类型
		// boolean b = (boolean)in;
		Object obj = "Hello";
		// obj变量的编译类型为Object,Object与String存在继承关系,可以强制类型转换
		// 而且obj变量实际上类型是String类型,所以运行时也可通过
		String objStr = (String)obj;
		System.out.println(objStr);
		// 定义一个objPri变量,编译类型为Object,实际类型为Integer
		Object objPri = Integer.valueOf(5);
		// objPri变量的编译时类型为Object,objPri的运行时类型为Integer,Object与Integer存在继承关系
		// 可以强制类型转换,而objPri变量实际上类型是Integer类型,
		// 所以下面代码运行时引发ClassCastException异常
		String str = (String)objPri;
	}
}

 instanceof运算符

instanceof运算符前一个是引用变量,后一个是一个类型。用于判断前面的对象是不是后面的类,或者是不是他的子类,或者实现类的实例。如果是,返回true
前面操作数的编译时候的类型要么和后面的类型相同,要么和后面的有父子继承关系(不一定是子类,可以是父类),否则编译有错误。
编译想通过的话,只要有继承关系就行。想返回true,前面的不能是后 面的父类。

  限制: instanceof,只能在两个类型具有继承关系才可判断,否则编译时就会报错。

public class InstanceofTest
{
	public static void main(String[] args)
	{
		// 声明hello时使用Object类,则hello的编译类型是Object,
		// Object是所有类的父类, 但hello变量的实际类型是String
		Object hello = "Hello";
		// String与Object类存在继承关系,可以进行instanceof运算。返回true。
		System.out.println("字符串是否是Object类的实例:"
			+ (hello instanceof Object));
		System.out.println("字符串是否是String类的实例:"
			+ (hello instanceof String)); // 返回true。
		// Math与Object类存在继承关系,可以进行instanceof运算。返回false。
		System.out.println("字符串是否是Math类的实例:"
			+ (hello instanceof Math));
		// String实现了Comparable接口,所以返回true。
		System.out.println("字符串是否是Comparable接口的实例:"
			+ (hello instanceof Comparable));
		String a = "Hello";
//		// String类与Math类没有继承关系,所以下面代码编译无法通过
//		System.out.println("字符串是否是Math类的实例:"
//			+ (a instanceof Math));
	}
}

十一、 继承和组合

使用继承的注意点

  1. 尽量隐藏父类的内部数据,父类的所有成员变量设置private,不让子类访问
  2. 不要让子类可以随便访问更改父类的方法。辅助方法设置成private;
    如果父类中的方法需要被外部类调用,但是又不想让子类重写这个方法,可以加final;
    如果希望子类重写该方法,但是不希望其他类自由调用,用protected
  3. 父类的构造器里面不要有子类会修改的方法。否则容易出现空指针。
    因为创建子类的对象的时候,一定会先调用父类的构造器,构造器里面调用的方法已经被子类覆盖,所以运行时用的方法是子类的方法。但是子类还没有创建,因此会有空指针的可能性。

什么时候从父类派生子类? 

  1. 增加了新的属性
  2. 增加了新的方法

使用组合来实现复用(看不懂 )

组合是一种“has”的关系,譬如人有胳膊,腿。
组合和继承的开销一样。     
至于怎么用的,书里面写的太少了。没有看明白
先定义private的父类的成员变量,然后在子类的构造器的形参中加入这个成员变量。然后把父类里面所有的public再写一次。(父类子类并不是这样,只是我不知道怎么能够说清楚) 

十二、初始化块

使用初始化块

当创建java对象时,先调用类里定义的初始化块,

      类中可以总包含5个成员:

       - field

       - 方法

       - 构造器

       - 初始化块。

       - 内部类(接口|枚举)

      初始化块, 它是属于类的一种成员。
    

  [修饰符]

 {

            执行性的代码。

      }

 

      初始化块没有名字!不能主动调用它, 它会在合适时候时会自动执行。

      修饰符 - 只能是static。有static修饰,表明该初始化块属于类, 类初始怀块(静态初始化块)

                     没有static修饰,表明该初始化块属于对象, 实例初始怀块(非静态初始化块)  

     先执行初始化块或声明实例变量时指定的初始值,再执行构造器里指定的初始值

实例初始化块(非静态初始化块)

  实例初始化块:

           1. 每次创建对象时,总会先执行实例初始化块、在执行对应构造器中的代码。

           2. 无论你调用哪个构造器,执行构造器代码之前,总会执行实例初始化块。

类初始怀块(静态初始化块)

静态初始化总是比普通初始化块先执行,

先执行顶层父类的静态初始化块,再执行其直接父类的静态初始化块,最后再执行本身的静态初始化,

再开始执行最顶层父类的初始化块、构造器,再执行父类的初始化块、构造器,最后再执行本身的初始化和构造器

           静态初始化块不能访问非静态成员

      静态初始化块:

           1. 它负责对类进行初始化,只在该类初始化才执行。 

              在一次JVM进程中,类只在第一次主动使用时加载、初始化,因此类初始化块只有一次执行机会。 

public class StaticInitTest
{
	// 先执行静态初始化块将a静态成员变量赋值为6
	static
	{
		a = 6;
	}
	// 再将a静态成员变量赋值为9
	static int a = 9;
	public static void main(String[] args)
	{
		// 下面代码将输出9
		System.out.println(StaticInitTest.a);
	}
}


 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值