第五章 类(1.2)

Java是一种纯粹的面向对象的语言,每一个程序至少包含一个类,所有的数据(属性)和操作(行为)都封装到类中。

类声明在Java源文件中(扩展名为.java的文本文件),一个源文件中只能声明一个公开的类(用public关键词修饰的类),源文件名必须与该类名相同。同时,一个源文件中也可以声明多个非公开类。

声明类,也就是创建了一种新的引用数据类型。

2.1、普通类

Java中用关键字class来声明类。

声明普通类的语法格式:

[权限修饰符] class 类名 {
	// 成员变量声明部分,即属性
	// 成员方法声明部分,即行为
}

例1:学生类

public class Students {
//	学生属性
	String id;
	String name;
	int age;

//	 含参构造方法
	public Students(String id, String name, int age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
//  无参构造方法
	public Students() {
	}

//	 成员方法
	public void introduce() {
		System.out.printf("学号:%s姓名:%s年龄:%d\n",id,name,age);
	}
}

测试类Test_stu:

public class Test_stu {

	public static void main(String[] args) {
//		创建Students对象
		Students stu1 = new Students("001","唐飞飞",28);	
		Students stu2 = new Students();
		
		stu1.introduce();
		stu2.introduce();
	}
}

解释:

注意:

  1. 构造方法的作用是为对象属性的初始化。(构造方法不允许使用对象调用,只能用new运算符实例化对象时自动调用。)
  2. 如果没有声明构造方法,系统会为类自动生成一个无参数的默认构造方法。并为该类的属性初始化默认值。(默认值:整型为0;浮点型为0.0;布尔型为false;字符型为’\u0000’;引用型为null
  3. 如果自定义了构造方法,则默认构造方法就不存在了。
  4. 一个类可以声明多个构造方法,但是各构造方法的参数不允许相同,在使用new运算符实例化对象时,根据参数匹配原则调用相应的构造方法。

特别注意:

1、构造方法与其它成员方法的不同点:

  • (1)、作用不同:构造方法仅用于实例化对象,对成员变量进行初始化,成员方法用于对成员变量进行多种操作。
  • (2)、调用方式不同:构造方法只能通过new运算符调用,成员方法可以通过对象调用。

2、super()代表执行父类无参数构造方法内容.

3、Java 中this()super()的使用注意
使用super和this应该注意这些:

  • 1)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
  • 2)super()this()类似,区别是,super从子类中调用父类的构造方法,this()在同一类内调用其它方法。
  • 3)super()this()均需放在构造方法内第一行。
  • 4)尽管可以用this调用一个构造器,但却不能调用两个。
  • 5)thissuper不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
  • 6)this()super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
    7)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

4、为什么this或者super要放到第一行?

  • this()、 super()是你如果想用传入当前构造器中的参数或者构造器中的数据调用其他构造器或者控制父类构造器时使用的,在一个构造器中你只能使用this()或者super()之中的一个,而且调用的位置只能在构造器的第一行,在子类中如果你希望调用父类的构造器来初始化父类的部分,那就用合适的参数来调用super(),如果你用没有参数的super()来调用父类的构造器(同时也没有使用this()来调用其他构造器),父类缺省的构造器会被调用,如果父类没有缺省的构造器,那编译器就会报一个错误。

2.2、抽象类

2.2.1、抽象类是什么?

在使用面向对象思想描绘现实世界的过程中,有时会遇到这样的情况:设想中的一个类没有足够的信息来描绘一个具体对象的行为。比如有一个类用来描绘所有的动物,该类中有一个方法用来描绘动物发出叫声的行为,当动物类作为基类,派生出狗类及猫类时,狗类猫类中可以分别具体描绘狗和猫是怎样发出叫声的,而单就动物类而言,由于动物的范围很笼统,便没有办法描绘动物具体怎样发出叫声。

在Java中,可以创建这样的类,该类中可以没有足够的信息来描绘一个具体对象的行为,这样的类便是抽象类

2.2.2、声明和使用抽象类

Java中,使用abstractclass关键字即可。

声明抽象类的语法格式:

[权限修饰符] abstract class 类名 {
	// 类体
}

例1:Animal类的源码:

/**
 * 动物类
 */
public abstract class Animal {
	// 属性
	String type;    // 类型
	String breed;   // 品种
	String name;    // 名称
	/**
	 * 无参构造函数
	 */
	public Animal() {
		super();
	}
	
	/**
	 * 含参构造函数
	 * @param type  类型
	 * @param breed 品种
	 * @param name  名称
	 */
	public Animal(String type, String breed, String name) {
		this.type = type;
		this.breed = breed;
		this.name = name;
	}
}

Test类的源码:

/**
 * 测试类
 */
public class Test {
	public static void main(String[] args) {
		// Animal animal = new Animal() ;  // 这句代码报错,抽象类不能被直接实例化
	}
}

注意:
1、当一个类是抽象类时,它便不能再被用来实例化对象。(本例中,Animal类是一个抽象类,使用语句new Animal()试图实例化Animal类的对象时会报错。)
2、抽象类除了不能被用来实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

2.2.3、声明抽象方法

在抽象类的基础上,需要使用抽象方法来描述缺少的行为信息。抽象方法就是只有方法头(方法声明),缺少方法体的方法,声明抽象方法仍然使用abstract关键字。

声明抽象方法的语法格式:

[权限修饰符] abstract 返回值类型 方法名称([参数列表]);

例2:修改后Animal类的源码:

/**
 * 动物类
 */
public abstract class Animal {
	// 属性
	String type;    // 类型
	String breed;   // 品种
	String name;    // 名称

	/**
	 * 含参构造函数
	 * @param type  类型
	 * @param breed 品种
	 * @param name  名称
	 */
	public Animal(String type, String breed, String name) {
		this.type = type;
		this.breed = breed;
		this.name = name;
	}
	/**
	 * 无参构造函数
	 */
	public Animal() {
		super();
	}
	/**
	 * 抽象方法,发出声音
	 */
	public abstract void makeSound();
}

解释:
本例中,Animal类中的makeSound()方法是一个抽象方法,它没有方法体。

注意:

  • 1、抽象类可以包含普通的方法,也可以不包含普通的方法。
  • 2、抽象类可以包含抽象方法,也可以不包含抽象方法。
  • 3、如果一个类中包含抽象方法,那么这个类必须是抽象类。
  • 4、构造方法,类成员方法(用 static修饰的方法)不能声明为抽象方法。

2.2.4、继承抽象类

由于抽象类不能被用来实例化对象,所以抽象类必须作为基类,被派生类继承后,才能被使用。

正因如此,开发人员通常需要在设计阶段慎重考虑哪些类要设计为抽象类。
示例:
Animal类的源码同上例,不再赘述。
Dog类的源码:

/**
 * 狗类
 */
public class Dog extends Animal {
	/**
	 * 派生类构造函数
	 * @param breed 品种
	 * @param name  名称
	 */
	public Dog(String breed, String name) {
		super("狗", breed, name);
	}

	/**
	 * 重写基类的makeSound()方法
	 */
	@Override
	public void makeSound() {
		System.out.printf("%s发出叫声,汪汪汪。\n", name);
	}
}

Cat类的源码:

/**
 * 猫类
 */
public class Cat extends Animal {
	/**
	 * 派生类构造函数
	 * @param breed 品种
	 * @param name  名称
	 */
	public Cat(String breed, String name) {
		super("猫", breed, name);
	}
	/**
	 * 重写父类的makeSound()方法
	 */
	@Override
	public void makeSound() {
		System.out.printf("%s发出叫声,喵喵喵。\n", name);
	}
}

测试类PetShop类的源码:

/**
 * 宠物店(测试类)
 */
public class PetShop {
	/**
	 * 用来测试的main方法
	 */
	public static void main(String[] args) {
		// 实例化Cat类和Dog类的对象,并将它们赋值给基类Animal类的变量
		// Cat类和Dog类各自完整的实现了Animal类中的抽象方法
		Animal animal1 = new Cat("波斯猫", "大花");
		Animal animal2 = new Dog("牧羊犬", "大黑");
		
		// 使用Animal类的变量调用Animal类中被派生类重写过的方法
		animal1.makeSound();
		animal2.makeSound();
	}
}

解释:

  • 1.本例中,Cat类和Dog类分别完整的实现(方法实现:给出每个抽象方法的方法体)了Animal类中的抽象方法,因此,Cat类和Dog类可以被用来实例化对象。
  • 2.抽象类的派生类只有实现了基类中所有的抽象方法后才能被用来实例化派生类对象。

注意:
如果有任何一个抽象方法未在派生类中被实现,该派生类也必须是抽象类。

2.2.5、抽象类和抽象方法总结

Java中抽象类的要点总结如下:

  • 1)、抽象类不能被实例化(编译无法通过)。只有抽象类的非抽象派生类可以被用来实例化对象。
  • (2)、抽象类中不一定包含抽象方法,但是包含抽象方法的类必定是抽象类。
  • (3)、抽象类中的抽象方法只是声明,不包含方法体(就是不给出方法的具体实现)。
  • (4)、构造方法,类成员方法(用 static修饰的方法)不能声明为抽象方法。
    抽象类的派生类必须给出抽象类中的抽象方法的具体实现,除非该派生类也是抽象类。
  • (5)、抽象类和抽象方法进一步诠释和巩固了Java面向对象编程核心特征中的多态性,使开发人员可以在设计、编码过程中更好的运用多态性。

2.3、内部类

2.3.1、内部类是什么

Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类

广泛意义上的内部类一般来说包括:

  • 成员内部类
  • 局部内部类
  • 匿名内部类

2.3.2、常见的内部类

2.3.2.1、成员内部类

成员内部类是最普通的内部类,它的定义位于另一个类的内部,和成员变量、成员方法同级。这里提到的另一个类也被称为外部类。成员内部类看起来就像是外部类的一个成员。

成员内部类也可以按照是否使用static修饰而分为实例成员内部类和静态成员内部类。

示例:
TestOutterClass类的源码:

// 外部类TestOutterClass
public class TestOutterClass {
    private String str = "我是TestOutterClass类中的成员变量str";
    public void print() {
        System.out.println("TestOutterClass类中的print()方法执行了");
        System.out.println("TestOutterClass类中的print()方法打印str:" + str);
    }

    // 内部类TestInnerClass
    class TestInnerClass {
        private String str = "我是TestInnerClass类中的成员变量str";
        public void print() {
            // print(); 内部类和外部类有相同名称和参数列表的方法,默认调用内部类的方法,这里会死循环,
            // 需要通过下面的语句调用外部类的print()方法
            TestOutterClass.this.print();
            System.out.println("TestInnerClass类中的print()方法执行了");
            System.out.println("TestInnerClass类中的print()方法打印str:" + str);
        }
    }
}

测试类Test类的源码:

package com.codeke.java.test;
public class Test {
    public static void main(String[] args) {
        TestOutterClass testOutterClass = new TestOutterClass();
        TestOutterClass.TestInnerClass testInnerClass = testOutterClass.new TestInnerClass();
        testInnerClass.print();
    }
}

执行输出结果:

TestOutterClass类中的print()方法执行了
TestOutterClass类中的print()方法打印str:我是TestOutterClass类中的成员变量str
TestInnerClass类中的print()方法执行了
TestInnerClass类中的print()方法打印str:我是TestInnerClass类中的成员变量str

解释:
1.没有被static修饰的成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员);而被static修饰的成员内部类只能访问外部类中同样被static修饰的成员。
2.没有被static修饰的成员内部类是依附外部类对象而存在的,也就是说,如果要创建该类型的成员内部类的对象,前提是必须存在一个外部类的对象。如同本例Test类main方法中那样;被static修饰的成员内部类并不依附外部类对象,创建该类型的内部类对象使用new 外部类名.内部类名()即可。
3.在成员内部类中,当成员内部类拥有和外部类同名的、非静态的成员变量或者方法(方法需要参数列表也相同)时,默认情况下访问的是内部类的成员。如果要访问外部类的同名成员,需要使用外部类名.this.成员名,如本例中的TestOutterClass.this.print()。(如果不同名,则可直接调用外部类的方法。)
4.没有被static修饰的成员内部类中不能存在静态成员(而是要定义为“静态终态”,如:static final String str1 = “我是内部类的静态成员”;),外部类中如果要访问该类型成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问;对于被static修饰的成员内部类,外部类中如果要访问该类型成员内部类的静态成员,可以不需要内部类对象,而访问非静态成员时,仍然需要内部类对象。
5.成员内部类作为外部类的成员,可以使用所有的访问权限修饰符,即public、protected、缺省、private。

2.3.2.2、局部内部类

局部内部类是定义在一个方法或者一个语句块里面的类,它的访问仅限于方法内或者语句块内。
下面是一个示例:
TestOutterClass类的源码:

public class TestOutterClass {
    public void print(){
        // 局部内部类
        class TestInnerClass {
            void print(){
                System.out.println("TestInnerClass类中的print()方法执行了");
            }
        }
        new TestInnerClass().print();
    }
}

测试类Test类的源码:

package com.codeke.java.test;
	public class Test {
	    public static void main(String[] args) {
	        new TestOutterClass().print();
	        }
	}

执行输出结果:

TestInnerClass类中的print()方法执行了

说明:
局部内部类就像是方法里面的一个局部变量一样,是不能用public、protected、private以及static修饰符修饰的。

2.3.2.3、匿名内部类

在开发过程中,如果接口或者抽象类中需要实现的方法很少,临时使用接口或者抽象类时,重新定义一个类单独实现方法又有些麻烦,可以直接使用匿名内部类对接口或者抽象类进行实现。
示例:
Printable接口的源码:

public interface Printable {
    void print();
}

测试类Test类的源码:

public class Test {
    public static void main(String[] args) {
        Printable printable = new Printable() {
            @Override
            public void print() {
                System.out.println("匿名内部类中的print()方法执行了");
            }
        };
        printable.print();
    }
}

执行输出结果:

匿名内部类中的print()方法执行了

解释:
本例中,自始至终并没有见到实现了Printable接口的类的类名,这也是该类被称为匿名内部类的原因。
需要注意,内部类丰富了开发人员继承类、实现接口的手段,并且利于数据安全和代码封装、隐藏,但内部类不利于代码复用,一定程度上增加了代码阅读难度,故在开发过程中,需要根据具体情况选择使用。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页