Java学习笔记(六):面向对象、接口和抽象类

类和对象

Java是一门面向对象的语言,下面我们来了解一下Java中的面向对象。

方法和重载

Java中的方法格式如下:

访问修饰符 返回值类型 方法名(参数){
    方法主体
}

Java的方法支持重载,当方法同名时,为了让编译器区别他们,至少需要下面之一不同:

  1. 参数个数不同;
  2. 对应位置上的参数类型不同;
  3. 不允许参数完全相同而只是返回值不同的情况出现;

可变参数

JDK1.5增加了新特性:可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。注意:可变参数必须位于最后一项。当可变参数个数多余一个时,必将有一个不是最后一项,所以只支持有一个可变参数。

 1 package org.hammerc.study;
 2 
 3 public class Main
 4 {
 5     public static void main(String [] args)
 6     {
 7         System.out.println(add(2,3));
 8         System.out.println(add(2,3,5));
 9     }
10     public static int add(int x, int ...args)
11     {
12         int sum = x;
13         for(int i = 0; i < args.length; i++)
14         {
15             sum += args[i];
16         }
17         return sum;
18     }
19 }

可变参数是可以指定类型。

参数默认值

由于“默认参数”和“方法重载”同时支持的话有二义性的问题,Java为了简单就不要“默认参数”了。

当然个人感觉参数默认值会更加简单一些。Java或许可以考虑学习C#,同时支持两个特性。

参数传值还是传引用

通常的说法是:对于基本数据类型(整型、浮点型、字符型、布尔型等),传值;对于引用类型(对象、数组)传引用。
基本类型传值,所有人都不会对此有疑义,但实际上,Java所有的参数传递都是传值,对于引用类型,其实参数传递时仍然是按值传递的;当然,按引用传递也不是完全没有道理,只是参考对象不是引用类型本身,而是引用类型所指向的对象。

面向对象

类是从中创建单个对象的蓝图,一个类可以包含以下任意变量类型。

  1. 局部变量: 方法里面,构造函数或块中定义的变量称为局部变量。该变量将被声明和初始化的方法中,当该方法完成该变量将被销毁。
  2. 实例变量: 实例变量都在一个类,但任何方法之外的变量。这些变量在类被加载的实例化。实例变量可以从内部的任何方法,构造函数或特定类别的块访问。
  3. 类变量: 类变量是在一个类中声明,任何方法之外的变量,用static关键字。

类提供的图纸对象。所以基本上一个对象是从一个类创建的。在Java中,关键字 new 用于创建新的对象。从类创建对象时有三个步骤:

  1. 声明: 变量声明,一个变量名的对象类型。
  2. 实例: “new”关键字是用来创建对象。
  3. 初始化: 关键字 new 后跟调用一个构造函数。这个调用初始化新的对象。

构造函数

在讨论关于类,其中最重要分课题之一是构造函数。每个类都有一个构造函数。如果我们不明确地写一个构造函数的类,Java编译器生成一个默认构造函数的类。

创建一个新的对象中的每个时间,至少有一个构造函数将被调用。构造函数的主要规则是,他们应该具有相同的名称作为类。一个类可以有多个构造函数。

访问权限

方法和属性

Java方法和属性有四种访问权限, 其中三种有访问权限修饰符,分别为private,public和protected,还有一种不带任何修饰符我们称其为default。

  • private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
  • default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。
  • protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
  • public:Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
 同一个类同一个包子类  任意类 
private   
default  
protected 
public

这里有两个需要注意的地方:

  1. default修饰的方法和属性子类在包外不能访问,在包内可以访问;
  2. protected修饰的方法和属性默认带有包内访问的功能,同一个包内也可调用;

Java类有两种访问权限,分别为public和不带任何修饰符的default。

  • public:可以供所有的类访问。
  • default:默认的访问权限是包级访问权限。

即如果写了一个类没有写访问权限修饰符,那么就是默认的访问权限,同一个包下的类都可以访问到,即使可以实例化该类 (当然如果这个类不具有实例化的能力除外,比如该类没有提供public的构造函数)。 

封装

封装是把过程和数据包围起来,对数据的访问只能通过已定义的接口。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。封装是一种信息隐藏技术,在java中通过关键字private实现封装。

get/set方法

我们直接看一下下面的例子:

 1 package org.hammerc.study;
 2 
 3 class A
 4 {
 5     public int num;
 6 }
 7 
 8 class B
 9 {
10     private int num;
11     public int getNum() { return num; }
12     public void setNum(int num) { this.num = num; }
13 }
14 
15 public class Main
16 {
17     public static void main(String [] args)
18     {
19         A a = new A();
20         a.num = 100;
21         
22         B b = new B();
23         b.setNum(100);
24     }
25 }

大家肯定会觉得类B是在没事找事做,为啥要这么写?

这样做是为了封装,使程序易于修改、维护,同时这样写能方便的扩展成观察者模式,备忘录模式。

比如现在你写成public的,所有的地方都直接访问,有一天,你需要在这个属性改变的时候做一些观察者的操作,比如发邮件通知,或者其他操作的时候你就杯具啦,你必须在工程中search到底多少地方调用过,并且在每个后面去加上一段sendEmailIfChange(userid,contents)代码,但是如果是封装了,就直接在set里面判断ifChange然后改了。 

如果用过C#会知道,C#本身支持get和set的写法,即外部看起来是对一个公开属性进行赋值或获取,实际上却是调用的get和set方法,这就避免了Java中的后期开发需要把公开属性改为方法时需要所有用到的地方都修改的尴尬。

匿名对象

匿名对象可以理解为只使用一次的对象,如下:

 1 package org.hammerc.study;
 2 
 3 class A
 4 {
 5     public void doSomething() { }
 6 }
 7 
 8 public class Main
 9 {
10     public static void main(String [] args)
11     {
12         new A().doSomething();
13     }
14 }

匿名对象一般用在执行一次就不需要的对象上。

继承

继承是所有OOP语言不可缺少的部分,在java中使用extends关键字来表示继承关系。当创建一个类时,总是在继承,如果没有明确指出要继承的类,就总是隐式地从根类Object进行继承。

继承限制

在Java中,继承有一些限制:

  1. 只支持单继承,即一个类只有一个父类,但可以有多个子类,整个类继承可以用树型结构来描述;
  2. 子类不能访问父类的private属性及方法;

对象实例化过程

当我们实例化一个对象时,都会先调用其父类的构造函数,如果没有直接写super表明调用,则会调用默认构造函数,如果调用了确切的父类构造函数则不会调用默认构造函数。

需要注意的是,父类的构造函数必须先进行调用,可以看下面的例子:

 1 public Obj(){
 2     a = 100;//在这句代码执行之前已经调用了父类的默认不带参数的构造函数了
 3 }
 4 
 5 public Obj(){
 6     super();//显示调用,效果和上面一致,可以省略
 7     a = 100;
 8 }
 9 
10 public Obj(){
11     a = 100;
12     super();//错误,父类构造函数必须写在第一行代码
13 }
14 
15 public Obj(){
16     super(100);//正确,同时指定调用的是父类的带一个 int 参数的构造函数
17     a = 100;
18 }

下面我们看看更详细的子类实例化过程

  1. 父类的static属性;
  2. 父类的static块;
  3. 子类的static属性;
  4. 子类的static块;
  5. 父类的普通属性;
  6. 父类的普通块;
  7. 父类的构造子;
  8. 子类的普通属性;
  9. 子类的普通块;
  10. 子类的构造子;

其中的1-4步是类相关静态内容初始化,5-10步是类的实例内容初始化,需要注意的是类的静态内容始终优先于实例内容被初始化。

 1 package basic;
 2 
 3 public class ClzInit {
 4     public static void main(String[] args) {
 5         new Son();
 6     }
 7 }
 8 class Parent {
 9     protected int n = 5;
10     protected static int m = 5;
11     static {
12         m = m * 2;
13         System.out.println("父类静态块调用;    m="+m);
14     }
15     {
16         n = n * 2;
17         m = m * 2;
18         System.out.print("父类普通块调用;");
19         System.out.print("n="+n);
20         System.out.println("    m="+m);
21     }
22 
23     public Parent() {
24         this.n = n * 10;
25         m = m + 10;
26         System.out.print("父类构造子;    n="+n);
27         System.out.println("    m="+m);
28     }
29 }
30 
31 class Son extends Parent {
32     private int sn=3;
33     private static int sm=3;
34     static {
35         m = m + 2;
36         sm=sm+2;
37         System.out.println("子类静态块调用;    m="+m);
38     }
39     {
40         n = n + 2;
41         sn=sn+2;
42         m = m + 2;
43         System.out.println("子类普通块调用;");
44         System.out.print("n="+n);
45         System.out.print("sn="+sn);
46         System.out.println("    m="+m);
47     }
48 
49     public Son() {
50         this.n = n + 10;
51         sn=sn+10;
52         m = m + 10;
53         System.out.print("子类构造子;n="+n);
54         System.out.println("    m="+m);
55     }
56 }

输出如下:

1 父类静态块调用;    m=10
2 子类静态块调用;    m=12
3 父类普通块调用;n=10    m=24
4 父类构造子;    n=100    m=34
5 子类普通块调用;
6 n=102sn=5    m=36
7 子类构造子;n=112    m=46

重写

属性

在Java中,类的属性是可以被重写的,有如下的规则:

  1. 静态变量和静态常量属于类,不属于对象,因此它们不能被覆盖;
  2. final常量可以被覆盖;
  3. 由于private变量不可以被子类继承,受访问权限的限制,它不能被覆盖;
  4. default、protected和public修饰符并不影响属性的覆盖;
  5. 属性的值取父类还是子类并不取决于我们创建对象的类型,而是取决于我们定义的变量的类型;

我们看下面的例子:

 1 package org.hammerc.study;
 2 
 3 public class Main {
 4     public static void main(String[] args) {
 5         Parent o1 = new Child();
 6         System.out.println("o1.n1: " + o1.n1);
 7         System.out.println("o1.n2: " + o1.getN2());
 8         System.out.println("o1.n3: " + o1.n3);
 9         System.out.println("o1.n4: " + o1.n4);
10         System.out.println("o1.n5: " + o1.n5);
11 
12         Child o2 = new Child();
13         System.out.println("o2.n1: " + o2.n1);
14         System.out.println("o2.n2: " + o2.getN2());
15         System.out.println("o2.n3: " + o2.n3);
16         System.out.println("o2.n4: " + o2.n4);
17         System.out.println("o2.n5: " + o2.n5);
18     }
19 }
20 class Parent  {
21     public final int n1 = 100;
22     private int n2 = 200;
23     int n3 = 300;
24     protected int n4 = 400;
25     public int n5 = 500;
26 
27     public int getN2(){
28         return n2;
29     }
30 }
31 class Child extends Parent {
32     protected final int n1 = 1000;
33     private int n2 = 2000;
34     public int n3 = 3000;
35     int n4 = 4000;
36     protected int n5 = 5000;
37 
38     public int getN2(){
39         return n2;
40     }
41 }

输出如下:

 1 o1.n1: 100
 2 o1.n2: 2000
 3 o1.n3: 300
 4 o1.n4: 400
 5 o1.n5: 500
 6 o2.n1: 1000
 7 o2.n2: 2000
 8 o2.n3: 3000
 9 o2.n4: 4000
10 o2.n5: 5000

在实际的开发中,属性被覆盖导致的bug比较难定位,所以我们要尽量避免出现属性覆盖的情况。

方法

方法重写(Override),即子类覆盖父类中的方法实现,属于下面会说到的多态。即允许子类改变父类的一些行为。

方法重写有以下几个需要注意的地方:

1.被覆盖方法的访问控制级别可以不一样:

  • 父类作用域为default的子类可以重写为default、protected和public;
  • 父类作用域为protecetd的子类可以写为protecetd和public;
  • 父类作用域为public的子类只能写为public;

即子类的访问级别必须要高于父类被覆盖方法的访问级别,如果父类是public的而子类是protected的则是错误的。

2.方法被定义为private或static或final的则不能被覆盖;

3.方法的返回类型:子类的返回类型可以是更具体的对象:

  • 比如父类的返回值是Object,子类可以指定为更具体的类型,但是反过来是错误的。
  • 另外说一点,如果方法参数类型不一致则会作为重载(Overload)处理,而不是重写(Override)。

4.在方法调用时先会在子类中找覆盖的方法,如果子类中没有则会在父类中去找。

我们来看一个例子:

 1 public class Parent {
 2     private int num(int i,int j){
 3         return i+j;
 4     }
 5     public static void main(String[] args) {
 6         Parent p = new Child();
 7         System.out.println(p.num(1, 2));
 8     }
 9 }
10 class Child extends Parent{
11     public int num(int x,int y){
12         return x-y;
13     }
14 }

这段代码的执行结果为什么呢?如果你回答-1则错了,正确答案是3。 为什么呢?因为父类的num方法是private的,所以不能被覆盖,所以子类的num方法不是一种Override,因此在子类找不到被覆盖的num方法就会执行父类的num方法。所以结果输出为3,之所以可以调用到private的num方法,是因为main就是Parent的静态方法。

如果不好理解可以再看一个例子:

 1 public class Main {
 2     public static void main(String[] args) {
 3         Parent p = new Child();
 4         System.out.println(p.num(1, 2));//报错,因为Parent类的num方法是私有的
 5     }
 6 }
 7 class Parent {
 8     private int num(int i,int j){
 9         return i+j;
10     }
11 }
12 class Child extends Parent{
13     public int num(int x,int y){
14         return x-y;
15     }
16 }

这样也是调不到子类的num方法,因为用的是父类的引用,如果是子类的引用则可以调用到。

super

super其实我们在上面的构造函数中已经说过了,其作用是调用父类的构造函数或方法。

和构造函数会默认调用父类的默认构造函数不一样,在方法中,如果不显示调用父类的方法,是不会默认调用父类的被覆盖的方法,如下:

 1 package org.hammerc.study;
 2 
 3 public class Main {
 4     public static void main(String[] args) {
 5         Parent p = new Child();
 6         p.num1();
 7         p.num2();
 8         p.num3();
 9     }
10 }
11 class Parent  {
12     public void num1() {
13         System.out.println("Parent num1");
14     }
15     public void num2() {
16         System.out.println("Parent num2");
17     }
18     public void num3() {
19         System.out.println("Parent num3");
20     }
21 }
22 class Child extends Parent {
23     public void num1() {
24         System.out.println("Child num1");
25     }
26     public void num2() {
27         super.num2();
28         System.out.println("Child num2");
29     }
30     public void num3() {
31         System.out.println("Child num3");
32         super.num3();
33     }
34 }

输出如下:

1 Child num1
2 Parent num2
3 Child num2
4 Child num3
5 Parent num3

@Override

在Java中,写在类或方法之前的@号称为注解,可以用来处理一些特殊的需求,更详尽的解释可以看下面两个文章:

http://blog.csdn.net/beyond0851/article/details/8520993

http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html

如果想重写父类的方法,比如toString()方法的话,在方法前面加上@Override  系统可以帮你检查方法的正确性:

@Override
public String toString(){...}

这是正确的,一旦写错成这样:

@Override
public String tostring(){...}

编译器可以检测出这种写法是错误的,这样能保证你的确重写的方法正确,而如果不加@Override的话编译器是不会报错的,它只会认为这是你自己新加的一个方法而已。

final

在Java中,final有以下几种用法:

修饰成员变量

当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变,但引用变量所指向的对象中的内容还是可以改变的。

其初始化可以在三个地方:

  1. 定义处,也就是说在final变量定义时直接给其赋值;
  2. 构造函数中。而且在Java1.1以前,只能是在定义时给值;
  3. 初如化代码块中{} 或者 static{};

修饰类

将final用于类身上时表示该类是无法被任何人继承的,那也就意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。对于final类中的成员变量,你可以定义其为final,也可以不是final。而对于方法,由于所属类为final的关系,自然也就成了final型的。你也可以明确的给final类中的方法加上一个final,但这显然没有意义。

修饰方法

将方法声明为final,那就说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。

另外有一种被称为inline的机制,它会使你在调用final方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final进行方法定义。

类中所有的private方法从某种意义上讲都是属于final的,因为他们在其它地方没法覆盖,你可以在一个private方法前加final修饰符,但没什么意义。

修饰参数

还有一种用法是定义方法中的参数为final,如果该参数为基本类型则其值不可改变,如果为引用类型,则不能将其指向其他对象,但可改变引用的对象的属性。

另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用,如下代码所示:

 1 public class INClass {
 2     void innerClass(final String str) {
 3         class IClass {
 4             IClass() {
 5                 System.out.println(str);
 6             }
 7         }
 8         IClass ic = new IClass();
 9     }
10 
11     public static void main(String[] args) {
12         INClass inc = new INClass();
13         inc.innerClass("Hello");
14     }
15 }

抽象类

对于面向对象编程来说,抽象是它的一大特征之一。在Java中,可以通过两种形式来体现OOP的抽象:抽象类和接口。

在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:

abstract void fun();

抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。

[public] abstract class ClassName {
    abstract void fun();
}

从这里可以看出,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。

包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:

  1. 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
  2. 抽象类不能用来创建对象;
  3. 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

在其他方面,抽象类和普通的类并没有区别。

接口

下面我们来说说接口:

不同于C++的多重继承,Java是单继承的语言,接口可以实现将不同类统一到一个类型下面的功能。

接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:

[public] interface InterfaceName {
}

接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:

class ClassName implements Interface1,Interface2,[....]{
}

可以看出,允许一个类遵循多个特定的接口。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。

抽象类和接口的区别

语法层面上的区别

  1. 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
  2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  3. 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
  4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

设计层面上的区别

抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

多态

多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。

多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。 

  1. 多态时,当子类覆盖了父类的方法,使用子类覆盖的方法;
  2. 当子类覆盖父类的成员变量时,父类方法使用的是父类的成员变量,子类方法使用的是子类的成员变量;

父类子类转换

子类转父类,由系统自己完成;

父类转子类,显示强制转换;

我们看看下面的例子:

 1 package org.hammerc.study;
 2 
 3 public class Main {
 4     public static void main(String[] args) {
 5         //隐式转换
 6         Child c1 = new Child();
 7         Parent p1 = c1;
 8         //显示转换
 9         Parent p2 = new Child();
10         Child c2  = (Child) p2;
11     }
12 }
13 class Parent  {
14 }
15 class Child extends Parent {
16 }

instanceof

instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。

需要注意的是,子类 instanceof 父类也会返回true。

引用的传递

参数传值还是传引用

通常的说法是:对于基本数据类型(整型、浮点型、字符型、布尔型等),传值;对于引用类型(对象、数组)传引用。
基本类型传值,所有人都不会对此有疑义,但实际上,Java所有的参数传递都是传值,对于引用类型,其实参数传递时仍然是按值传递的;当然,按引用传递也不是完全没有道理,只是参考对象不是引用类型本身,而是引用类型所指向的对象。

this关键字

Java中this有3种使用情况:

表示当前对象和类中的属性或方法

1 class People  {
2     public String name;
3     public int age;
4 
5     public People(String name, int age) {
6         this.name = name;
7         this.age = age;
8     }
9 }

如果参数和类的属性同名,需要添加this来表示当前对象的属性,否则按参数来处理;

表示当前类中的构造函数

 1 class People  {
 2     public String name;
 3     public int age;
 4     public int sex;
 5 
 6     public People() {
 7         System.out.println("无参数构造函数");
 8     }
 9     
10     public People(String name, int age) {
11         this();
12         this.name = name;
13         this.age = age;
14     }
15 
16     public People(String name, int age, int sex) {
17         this(name, age);
18         this.sex = sex;
19     }
20 }

通过this可以调用到对应的构造函数,需要注意几点:

  • 只有构造函数才能调用this;
  • this必须是第一行;
  • this和super不能共存;

static关键字

使用static声明的属性和方法称为全局属性和方法,可以在静态方法和实例方法中进行访问和调用,而静态方法中只能访问static定义的属性和方法,不能访问非static定义的属性和方法。

static方法

static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

static属性

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

static代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值