文章目录
  1. 1. 类与对象
    1. 1.1. 定义类
    2. 1.2. 构造方法
    3. 1.3. 默认构造方法
    4. 1.4. 重载构造方法
  2. 2. 继承(扩展)
  3. 3. 多态
  4. 4. 抽象
    1. 4.1. 抽象类的定义及与普通类的异同
    2. 4.2. 接口的定义与实现
    3. 4.3. 抽象类和接口在继承中的语法特征

类与对象

类是客观存在的,是抽象的,概念的东西。对象时具体的,代表一个事物。例如:车是一个类,汽车,自行车就是它的一个对象。类是对象的模板,对象是类的一个个体。

定义类

类定义时使用class关键字,例如定义一个衣服类,它具有颜色和尺寸等属性。

1
2
3
4
class Clothes{
String color;
char size;
}

  1. 对于public修饰符,它具有最大的访问权限,可以访问任何一个在classpath的类、接口,异常等,它往往用于对外的情况,也就是对象或类对外的一种接口的形式。
  2. 对于protected修饰符,它主要的作用就是用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西。
  3. 对于private来说,它的访问权限仅限于类内部,是一种封装的体现,例如,大多数的成员变量就是修饰符为private的,它们不希望被其他任何外部的类访问。
  4. 对于default来说,是声明时没有加修饰符,认为friendly,它是针对本包访问而设计的,任何处于本包下的类、接口、异常等、都是互相访问、即使是父类没有用protected修饰的成员也可以。

下表为Java访问控制符的含义和使用情况

类内部 本包 字类 外部包
public
protected ×
private × × ×
default × ×

构造方法

构造方法一般需要满足以下几条规则:
(1)方法名必须与类名保持一致。
(2)不要声明返回类型。
(3)不能被static, final, synchronized, abstract和native修饰。构造方法不能被子类继承,所以用final和abstract修饰没有任何意义。构造方法用于初始化一个新建对象,所以用static修饰没有意义。多个线程不会同时创建内存地址相同的对象,因此使用synchronized修饰也是没有必要的。此外,Java语言目前还不支持native类型的构造方法。

知识扩展:

用this语句来调用其他构造方法时,必须遵守以下规则:
(1)假如在一个构造方法中使用了this语句,那么它必须作为构造方法的第一条语句(不考虑注释语句)。
(2)只能在一个构造方法中使用this语句来调用类的其他构造方法,而不能在实例方法中用this语句来调用类的其他构造方法。
(3)只能用this语句来调用其他构造方法,而不能通过方法名来直接调用构造方法。
同样,使用super语句调用父类的构造方法时,也必须遵守以下语法规则:
(1)在子类的构造方法中,不能直接通过父类方法名来调用父类的构造方法,而是使用super语句。
(2)假如子类的构造方法中有super语句,它必须作为构造方法的第一条语句。
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Sample {
private int x;
public Sample() { // 不带参数的构造方法
this(1);
}
public Sample(int x) { //带参数的构造方法
this.x=x;
}
public int Sample(int x) { //不是构造方法
return x++;
}
}

默认构造方法

我们知道,java语言中规定每个类至少要有一个构造方法,为了保证这一点,当用户没有给java类定义明确的构造方法的时候,java为我们提供了一个默认的构造方法,这个构造方法没有参数,修饰符是public并且方法体为空。
其实默认的构造方法还分为两种,一种就是刚刚说过的隐藏的构造方法,另一种就是显示定义的默认构造方法.
如果一个类中定义了一个或者多个构造方法,并且每一个构造方法都是带有参数形式的,那么这个类就没有默认的构造方法,看下面的例子。

1
2
3
4
5
6
7
8
9
public class Sample1{}
public class Sample2{
public Sample2(int a){System.out.println("My Constructor");}
}
public class Sample3{
public Sample3(){System.out.println("My Default Constructor");}
}

上面的三个类中Sample1有一个隐式的默认构造方法,下列语句Sample1 s1=new Sample()合法;
Sample2没有默认的构造方法,下列语句Sample2 s2=new Sample2()不合法,执行会编译错误
Sample3有一个显示的默认构造方法,所以以下语句Sample3 s3=new Sample3();合法.

重载构造方法

当通过new语句创建一个对象时,在不同的条件下,对象可能会有不同的初始化行为。例如对于公司新进来的一个雇员,在一开始的时候,有可能他的姓名和年龄是未知的,也有可能仅仅他的姓名是已知的,也有可能姓名和年龄都是已知的。如果姓名是未知的,就暂且把姓名设为”无名氏”,如果年龄是未知的,就暂且把年龄设为-1。

可通过重载构造方法来表达对象的多种初始化行为。以下例程的Employee类的构造方法有三种重载形式。在一个类的多个构造方法中,可能会出现一些重复操作。为了提高代码的可重用性,Java语言允许在一个构造方法中,用this语句来调用另一个构造方法。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Employee {
private String name;
private int age;
/** 当雇员的姓名和年龄都已知,就调用此构造方法 */
public Employee(String name, int age) {
this.name = name;
this.age=age;
}
/** 当雇员的姓名已知而年龄未知,就调用此构造方法 */
public Employee(String name) {
this(name, -1);
}
/** 当雇员的姓名和年龄都未知,就调用此构造方法 */
public Employee() {
this( "无名氏" );
}
public void setName(String name){this.name=name; }
public String getName(){return name; }
public void setAge(int age){this.age=age;}
public int getAge(){return age;}
}

继承(扩展)

参考 面向对象高级(一)

多态

多态的两种表现形式:重载和重写。

  1. 重载:是发生在同一类中,具有相同的方法名,主要是看参数的个数,类型,顺序不同实现方法的重载的,返回值的类型可以不同。
  2. 重写:是发生在两个类中(父类和子类),具有相同的方法名,主要看方法中参数,个数,类型必须相同,返回值的类型必须相同。

要理解多态性,首先要知道什么是“向上转型”。
我定义了一个子类Cat,它继承了Animal类,那么后者就是前者是父类。我可以通过

  Cat c = new Cat();

实例化一个Cat的对象,这个不难理解。但当我这样定义时:

  Animal a = new Cat();

这代表什么意思呢?
很简单,它表示我定义了一个Animal类型的引用,指向新建的Cat类型的对象。由于Cat是继承自它的父类Animal,所以Animal类型的引用是可以指向Cat类型的对象的。那么这样做有什么意义呢?因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特,
定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的;
同时,父类中的一个方法只有在在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用;对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。
例子:

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
29
30
class Father{
  public void func1(){
  func2();
  }
  //这是父类中的func2()方法,因为下面的子类中重写了该方法
  //所以在父类类型的引用中调用时,这个方法将不再有效
  //取而代之的是将调用子类中重写的func2()方法
  public void func2(){
  System.out.println("AAA");
  }
  }
  class Child extends Father{
  //func1(int i)是对func1()方法的一个重载
  //由于在父类中没有定义这个方法,所以它不能被父类类型的引用调用
  //所以在下面的main方法中child.func1(68)是不对的
  public void func1(int i){
  System.out.println("BBB");
  }
  //func2()重写了父类Father中的func2()方法
  //如果父类类型的引用中调用了func2()方法,那么必然是子类中重写的这个方法
  public void func2(){
  System.out.println("CCC");
  }
  }
  public class PolymorphismTest {
  public static void main(String[] args) {
  Father child = new Child();
  child.func1();//打印结果将会是什么?
  }
  }

上面的程序是个很典型的多态的例子。子类Child继承了父类Father,并重载了父类的func1()方法,重写了父类的func2()方法。重载后的func1(int i)和func1()不再是同一个方法,由于父类中没有func1(int i),那么,父类类型的引用child就不能调用func1(int i)方法。而子类重写了func2()方法,那么父类类型的引用child在调用该方法时将会调用子类中重写的func2()。

抽象

抽象类的定义及与普通类的异同

抽象类的定义及使用规则

  • 包含一个抽象方法的类必须是抽象类;
  • 抽象类和抽象方法都要使用abstract关键字声明;
  • 抽象方法只需要声明不需要实现;
  • 抽象类不能被直接实例化。抽象类必须被子类继承,子类(如果不是抽象类)必须覆写抽象类中的全部抽象方法。

抽象类的定义格式

1
2
3
4
5
6
7
8
abstract class 抽象类名称{
属性;
访问权限 返回值类型 方法名称(参数){ //普通方法
[return 返回值];
}
访问权限abstract返回值类型 方法名称(参数); //抽象方法
//在抽象类方法中是没有具体方法的
}

接口的定义与实现

  1. 定义接口使用interface来定义一个接口。接口定义同类的定义类似,也是分为接口的声明和接口体,其中接口体由常量定义和方法定义两部分组成。定义接口的基本格式如下:
1
2
3
4
5
[修饰符] interface 接口名 [extends 父接口名列表]{
[public] [static] [final] 常量;
[public] [abstract]方法;
}

例如,定义一个用于计算的接口,在该接口中定义了一个常量PI和两个方法,具体代码如下:

1
2
3
4
5
6
public interface CalInterface
{
final float PI=3.14159f;//定义用于表示圆周率的常量PI
float getArea(float r);//定义一个用于计算面积的方法getArea()
float getCircumference(float r);//定义一个用于计算周长的方法getCircumference()
}

  1. 实现接口,接口在定义后,就可以在类中实现该接口。在类中实现接口可以使用关键字implements,其基本格式如下:
1
2
3
4
[修饰符] class <类名> [extends 父类名] [implements 接口列表]{
................
}

例如,编写一个名称为Cire的类,该类实现上面定义的接口Calculate,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Cire implements CalInterface
{
public float getArea(float r)
{
float area=PI*r*r;//计算圆面积并赋值给变量area
return area;//返回计算后的圆面积
}
public float getCircumference(float r)
{
float circumference=2*PI*r; //计算圆周长并赋值给变量circumference
return circumference; //返回计算后的圆周长
}
public static void main(String[] args)
{
Cire c = new Cire();
float f = c.getArea(2.0f);
System.out.println(Float.toString(f));
}
}

抽象类和接口在继承中的语法特征

类是对象的模板,抽象类和接口可以看做是具体的类的模板。
由于从某种角度讲,接口是一种特殊的抽象类,它们的渊源颇深,有很大的相似之处,所以在选择使用谁的问题上很容易迷糊。我们首先分析它们具有的相同点。

  • 都代表类树形结构的抽象层。在使用引用变量时,尽量使用类结构的抽象层,使方法的定义和实现分离,这样做对于代码有松散耦合的好处。
  • 都不能被实例化。
  • 都能包含抽象方法。抽象方法用来描述系统提供哪些功能,而不必关心具体的实现。
  1. 抽象类可以为部分方法提供实现,避免了在子类中重复实现这些方法,提高了代码的可重用性,这是抽象类的优势;而接口中只能包含抽象方法,不能包含任何实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class A{
public abstract void method1();
public void method2(){
//A method2
}
}
public class B extends A{
public void method1(){
//B method1
}
}
public class C extends A{
public void method1(){
//C method1
}
}

抽象类A有两个子类B、C,由于A中有方法method2的实现,子类B、C中不需要重写method2方法,我们就说A为子类提供了公共的功能,或A约束了子类的行为。method2就是代码可重用的例子。A 并没有定义 method1的实现,也就是说B、C 可以根据自己的特点实现method1方法,这又体现了松散耦合的特性。
再换成接口看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface A{
public void method1();
public void method2();
}
public class B implements A{
public void method1(){
//B method1
}
public void method2(){
//B method2
}
}
public class C implements A{
public void method1(){
//C method1
}
public void method2(){
//C method2
}
}

接口A无法为实现类B、C提供公共的功能,也就是说A无法约束B、C的行为。B、C可以自由地发挥自己的特点现实 method1和 method2方法,接口A毫无掌控能力。

  1. 一个类只能继承一个直接的父类(可能是抽象类),但一个类可以实现多个接口,这个就是接口的优势。
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
29
30
interface A{
public void method2();
}
interface B{
public void method1();
}
class C implements A,B{
public void method1(){
//C method1
}
public void method2(){
//C method2
}
}
//可以如此灵活的使用C,并且C还有机会进行扩展,实现其他接口
A a=new C();
B b=new C();
abstract class A{
public abstract void method1();
}
abstract class B extends A{
public abstract void method2();
}
class C extends B{
public void method1(){
//C method1
}
public void method2() {
//C method2
}
文章目录
  1. 1. 类与对象
    1. 1.1. 定义类
    2. 1.2. 构造方法
    3. 1.3. 默认构造方法
    4. 1.4. 重载构造方法
  2. 2. 继承(扩展)
  3. 3. 多态
  4. 4. 抽象
    1. 4.1. 抽象类的定义及与普通类的异同
    2. 4.2. 接口的定义与实现
    3. 4.3. 抽象类和接口在继承中的语法特征