*/

.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #f8f8f8;
}

.hljs-comment,
.hljs-template_comment,
.diff .hljs-header,
.hljs-javadoc {
color: #998;
font-style: italic;
}

.hljs-keyword,
.css .rule .hljs-keyword,
.hljs-winutils,
.javascript .hljs-title,
.nginx .hljs-title,
.hljs-subst,
.hljs-request,
.hljs-status {
color: #333;
font-weight: bold;
}

.hljs-number,
.hljs-hexcolor,
.ruby .hljs-constant {
color: #099;
}

.hljs-string,
.hljs-tag .hljs-value,
.hljs-phpdoc,
.tex .hljs-formula {
color: #d14;
}

.hljs-title,
.hljs-id,
.coffeescript .hljs-params,
.scss .hljs-preprocessor {
color: #900;
font-weight: bold;
}

.javascript .hljs-title,
.lisp .hljs-title,
.clojure .hljs-title,
.hljs-subst {
font-weight: normal;
}

.hljs-class .hljs-title,
.haskell .hljs-type,
.vhdl .hljs-literal,
.tex .hljs-command {
color: #458;
font-weight: bold;
}

.hljs-tag,
.hljs-tag .hljs-title,
.hljs-rules .hljs-property,
.django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal;
}

.hljs-attribute,
.hljs-variable,
.lisp .hljs-body {
color: #008080;
}

.hljs-regexp {
color: #009926;
}

.hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.lisp .hljs-keyword,
.tex .hljs-special,
.hljs-prompt {
color: #990073;
}

.hljs-built_in,
.lisp .hljs-title,
.clojure .hljs-built_in {
color: #0086b3;
}

.hljs-preprocessor,
.hljs-pragma,
.hljs-pi,
.hljs-doctype,
.hljs-shebang,
.hljs-cdata {
color: #999;
font-weight: bold;
}

.hljs-deletion {
background: #fdd;
}

.hljs-addition {
background: #dfd;
}

.diff .hljs-change {
background: #0086b3;
}

.hljs-chunk {
color: #aaa;
}

#container {
padding: 15px;
}
pre {
border: 1px solid #ccc;
border-radius: 4px;
display: block;
background-color: #f8f8f8;
}
pre code {
white-space: pre-wrap;
}
.hljs,
code {
font-family: Monaco, Menlo, Consolas, 'Courier New', monospace;
}
:not(pre) > code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
white-space: nowrap;
border-radius: 4px;
}
-->

基础

类有属性和方法,它们对本类有效(作用范围)。类的属性就是成员变量,它默认会赋值初始化。类的方法是类具有的一些行为。

类是抽象的,将它们实例化后就是对象(通过new进行实例化),各实例化后的对象都具有这些成员变量的属性,且赋有具体的值,如果某对象没有为成员变量赋值,则采用默认初始化时的值。每个new出来的对象都有自己独立的成员变量,但某个类的所有对象都共享类的方法,因为类方法只是一段放在代码区的代码,只有执行调用类方法时才会产生相关内容。

有了实例化后的对象,就可以引用对象的属性并调用对象的方法(实际上是类的方法,方法是共享的,并不属于某个单独的对象),这样就可以实现这个对象的相关操作。引用对象的属性方式为"对象名.成员变量",调用对象的方法的方式为"对象名.方法"。虽说方法是各对象共享的,但显然,"对象名1.方法1"的方法1执行时,方法内部的变量采用的都是对象名1的成员变量。

示例分析:

以一个三维空间上的点类来说,点具有三维坐标xyz,x、y、z就是它们的属性,需要定义为点类的成员变量。点可以求出它到原点的距离、到另一个点的距离,求距离就是通过类的方法(函数)实现。通过new这个点类,就可以实实在在地创建一个点,new一次就一个点,每个点都有自己的xyz属性,每个点的成员变量都在new出来的时候和对象一起存放在heap内存区。每个点都拥有大家共享的求距离方法getDistance()。于是,就可以将这个点类定义为下面的形式:

  1. class Point {
  2. //定义成员变量,即三个坐标,坐标可能是小数,因此定义为double类型
  3. double x;
  4. double y;
  5. double z;
  6. //定义构造方法,使得以后在new一个点对象的时候为点的成员变量xyz赋值
  7. Point(double _x,double _y,double _z) {
  8. x = _x;
  9. y = _y;
  10. z = _z;
  11. }
  12. //有了构造方法,就可以new对象的时候赋值,例如赋值点p对象的xyz分别为1/2/3:
  13. //Point p = new Point(1.0,2.0,3.0);
  14. //定义求两点间距离的方法getDistance()。
  15. //涉及到两个点:一个是调用该方法的点对象自身,一个是目标点对象
  16. //因此,需要将目标点对象作为方法的形参,并使用目标点的坐标属性
  17. double getDistance(Point px) {
  18. return (x-px.x)*(x-px.x)+(y-px.y)*(y-px.y)+(z-px.z)*(z-px.z);
  19. }
  20. //有了方法,以后就可以调用该方法求距离,例如,求点(1,2,3)到原点(0,0,0)的距离
  21. //Point p = new Point(1.0,2.0,3.0);
  22. //Point p1 = new Point(0.0,0.0,0.0);
  23. //System.out.println(p.getDistance(p1));
  24. //这表示调用点p的方法,求点p到原点p1的距离,
  25. //p1的属性赋值给方法的形参px(px指向点p1对象),因此其坐标值为(0,0,0)
  26. //因为调用的是点p的方法,因此方法中的x/y/z是点p的成员变量值,即(1,2,3)
  27. }

将上述代码整理,并写一个main方法,就可以实现一个计算两点距离的小程序。例如,TestPoint.java文件内是如下内容:

  1. class Point {
  2. double x,y,z;
  3. Point(double _x,double _y,double _z) {
  4. x = _x;
  5. y = _y;
  6. z = _z;
  7. }
  8. double getDistance(Point p){
  9. return (x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)+(z-p.z)*(z-p.z);
  10. }
  11. }
  12. public class TestPoint {
  13. public static void main(String[] args) {
  14. Point p = new Point(1.0,2.0,3.0);
  15. Point p1 = new Point(1.0,0.0,0.0);
  16. System.out.println(p.getDistance(p1));
  17. }
  18. }

从上面的例子中可以感受到,面向对象更抽象地说是面向类。在实现某个功能的时候,例如求两个三维点之间的距离时,将点的属性和求距离的方法定义到点类中,以后就不用管点的xyz属性、求三维点之间距离的表达式方法,只要在需要时面向这个类new出点对象,它就有了点的xyz属性,再调用点对象的求距离的方法就可以了。有了面向对象,求三维点距离时,只需知道两件事:为成员变量xyz赋值;记得点类中的方法的名称。这就像为了查看文本内容执行cat命令一样,只要记得cat命令的名称、功能和选项参数即可,无需关心cat的内部机制是如何读取文本内容并将其显示出来的。

在定义一个方法时,需要考虑三个问题:方法的名称如何取、方法的参数、方法是否有返回值。方法的名称暂且不说,方法的参数必须要考虑清楚,例如求两点的距离时,参数可以是某个点的坐标,也可以直接是一个点对象。如果已经定义了点类,那么使用点对象作为参数更符合面向对象的原则;方法的返回值同样重要,返回值决定了这个方法的性质,例如判断两点间的距离是否大于20,就应该返回布尔类型,而不是double类型。

构造方法

构造方法和类同名,它的作用是在对象被new出来时做初始化行为。因为构造方法的目的是初始化,因此构造方法必须不能有返回值,即不能写上数据类型或void关键字。

例如:

  1. class Point {
  2. double x,y,z;
  3. //构造方法,注意其名称必须为Point()
  4. Point(double _x,double _y,double _z) {
  5. x = _x;
  6. y = _y;
  7. z = _z;
  8. }
  9. }

以后就可以在new对象的时候进行初始化,例如:

  1. Point p = new Point(1,2,3);

如果没有显式定义构造方法,则隐含了一个空构造方法,例如下面的代码中,两个class是完全等价的。

  1. class Point {
  2. double x,y,z;
  3. }
  4. class Point {
  5. double x,y,z;
  6. Point() {}
  7. }

因为初始化有默认的值,所以它们还等价于(0.0是初始化double时的默认值):

  1. class Point {
  2. double x,y,z;
  3. Point() {
  4. x = 0.0;
  5. y = 0.0;
  6. z = 0.0;
  7. }
  8. }

正因为有隐含的空构造方法,才能在new对象的时候不使用任何参数就能进行成员变量的初始化。例如:

  1. Point p = new Point();

在new对象的时候,对象的参数必须和构造方法完全对应,例如定义了构造方法Point(double _x,double _y,double _z)后,就只能new Point(value1,value2,value3),而不能不接任何参数new Point()或接少于3个的参数new Point(value1,value2)

方法的重载(overload)

当两个或多个方法的名称相同,只有参数不同时(可以是参数的个数、参数的数据类型不同),它们就构成了方法的重载。

方法的重载大大减少了方法数量的定义。例如,要求两个值中较大者,考虑到值可以是整形也可以是小数,于是使用如下方式:

  1. public class Num {
  2. void intMax(int a,int b) {
  3. System.out.println(a>b ? a : b);
  4. }
  5. void doubleMax(double a,double b) {
  6. System.out.println(a>b ? a : b);
  7. }
  8. public static void main(String[] args) {
  9. Num n = new Num();
  10. n.intMax(2,3);
  11. n.doubleMax(2.0,3.0);
  12. }
  13. }

这里第一个方法intMax()和第二个方法doubleMax()实际上是重复的,仅仅只是参数类型上不同。这样的设计很不方便,不仅在比较数值时不知道应该调用哪一个方法,还要知道各个方法的区别。

而使用重载就不再有这样的问题。

  1. public class Num {
  2. void Max(int a,int b) {
  3. System.out.println(a>b ? a : b);
  4. }
  5. void Max(double a,double b) {
  6. System.out.println(a>b ? a : b);
  7. }
  8. public static void main(String[] args) {
  9. Num n = new Num();
  10. n.Max(2,3);
  11. n.Max(2.0,3.0);
  12. }
  13. }

这两个Max方法名称相同,仅仅只是参数不同。在调用Max()的时候,根据传递的实参(2,3)和(2.0,3.0),它能能够区分出前者应该使用第一个Max(),后者使用第二个Max()。

重载的本质是在调用方法时能够通过传递的参数个数、参数的值筛选出具体应该使用哪个方法。

例如下面的方法中,前4个都能构成方法重载,而最后一个不能,因为它的定义方式不同。从本质上来说,是因为调用Max()传递两个int整型数值时,无法确定是选择第一个方法还是最后一个方法,而它们又正好是冲突的,因此它们不构成重载。

  1. void Max(int a,int b) {}
  2. void Max(int a,int b,int c) {}
  3. void Max() {}
  4. void Max(char a,char b)
  5. Max(int a,int b) {}

this关键字

在类的方法定义中使用this关键字可以代表该方法的对象的引用,它是new出来的对象中指向对象自身的关键字。当必须指出当前所使用方法的对象是谁时需要使用this,使用this还可以避免成员变量和形参重名的问题。

  1. public class TestThis {
  2. int i = 100;
  3. TestThis(int i) {
  4. this.i = i; //i为形参i(就近原则),this.i代表的是对象中的i,即成员变量i
  5. //this在此避免了成员变量和形参同名的问题
  6. }
  7. TestThis increment() {
  8. ++i; //由tt.increment()调用,因此i是成员变量
  9. return this; //返回的this代表TestThis类的对象自身
  10. //this在此表示对象自身
  11. }
  12. void print() {
  13. System.out.println("i=" + i);
  14. }
  15. public static void main(String[] args) {
  16. TestThis tt = new TestThis(10);
  17. System.out.println("i= " + tt.i);
  18. tt.increment().increment().print();
  19. }
  20. }

上述示例中,new TestThis创建了一个TestThis对象,tt指向该对象。tt.increment()表示调用一次tt对象的方法,此时i自增一次,并返回this,this代表的对象正是tt指向的对象,是TestThis类的对象,所以他也有increment()方法,所以还可以继续执行increment()方法,最后再次返回this,最后执行print()方法,输出自增两次后的i值。

注意,虽然可以return this来返回自身对象的引用,但却不能使用return super来返回父类对象的引用。也就是说,父类对象只能操作其内某个成员,不能直接返回父对象整体。

static关键字

static声明的成员变量为静态成员变量,它是该类的共享变量。在第一次使用时被初始化,对于该类的所有对象来说,static成员变量只有一份。

可以通过对象引用或直接通过类名来引用静态成员。即使在没有new出任何对象时,也能直接引用静态成员,因为它属于类。假如类名为T,静态成员变量有i,静态方法有m(),则可以直接使用T.i和T.m()分别引用。当new出来T的一个对象t时,可以使用t.i或t.m(),这和使用T.i和T.m()是完全等价的。(实际上,在不产生冲突的情况下,即使不写类名也可以直接引用静态变量、静态方法)

用static声明的方法为静态方法,静态方法不是针对某个对象来调用的。在调用静态方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员,即静态方法中不可以访问非静态成员变量和其他非静态方法。换句话说,因为静态方法属于类,静态方法看不到heap中各对象中的成员,它只能看到data segment中的静态成员。

  1. public class Student {
  2. static int cnt = 0; //static成员变量
  3. int id;
  4. String name;
  5. Student(String name) {
  6. id = ++cnt;
  7. this.name = name;
  8. }
  9. public void info() {
  10. System.out.println("id=" + id + ", name=" + name);
  11. }
  12. public static void main(String[] args) {
  13. Student.cnt = 100; //在new之前就可以使用类名直接引用static成员变量并赋值
  14. //还可以直接写为"cnt = 100;"
  15. Student s1 = new Student("Malongshuai");
  16. Student s2 = new Student("Gaoxiaofang");
  17. s1.info();
  18. s2.info();
  19. }
  20. }

在上面的例子中,静态变量cnt使用类名直接访问,并使用静态变量cnt作为成员变量id的赋值基础("id=++cnt;")。由于静态变量只在最初进行了赋值,后续一直都通过自增的方式进行改变,这是静态变量的广为使用的功能:"充当计数器"

如果把上面的static关键字去掉,并注释Student.cnt行,再编译运行,那么id的输出结果将总是1,因为cnt作为成员变量被初始化,所有对象的cnt都一样,从而导致id的值也一样。

无论是静态变量还是静态方法,它们都可以在new出对象之前直接引用,这时还不存在对象,因此在静态方法中无法使用非静态的成员变量(它们还不存在)。正如上面的public static void main(),它是静态的,可以直接引用cnt,它不需要在运行时先去new一个对象才能执行,否则main就太"不智能",每次执行都要先new出对象。

如果将static关键字去掉,在编译时将报如下错误:

  1. λ javac Student.java
  2. Student.java:16: 错误: 无法从静态上下文中引用非静态 变量 cnt
  3. Student.cnt=100;
  4. ^
  5. 1 个错误

继承

类与类之间能体现"什么是什么"的语义逻辑,就能实现类的继承。例如,猫是动物,那么猫类可以继承动物类,而猫类称为子类,动物类称为父类。

子类继承父类后,子类就具有了父类所有的成员,包括成员变量、方法。实际上在内存中,new子类对象时,heap中划分了一部分区域存放从父类继承来的属性。例如,new parent得到的区域A,new child得到的区域B,区域A在区域B中。

子对象中之所以包含父对象,是因为在new子对象的时候,首先调用子类构造方法构造子对象,在开始构造子对象的时候又首先调用父类构造方法构造父对象。也就是说,在形成子对象之前,总是先形成父对象,然后再慢慢的补充子对象中自有的属性。具体内容见"继承时构造方法的重写super()"。

子类不仅具有父类的成员,还具有自己独有的成员,例如有自己的方法、自己的成员变量。子类、父类中的成员名称不同时这很容易理解,但它们也可能是同名的。如果子类中有和父类继承的同名方法,例如父类有eat()方法,子类也有eat()方法,则这可能是方法的重写(见下文)。如果子类中的成员变量和父类的成员变量同名,则它们是相互独立的,例如父类有name属性,子类还自己定义了一个name属性,这是允许的,因为可以分别使用this和super来调用它们。

继承类时使用extends关键字。继承时,java只允许从一个父类继承。

  1. class Person {
  2. String name;
  3. int age;
  4. void eat() { System.out.println("eating...");}
  5. void sleep() {System.out.println("sleep...");}
  6. }
  7. class Student extends Person {
  8. int studentID;
  9. Student(int id,String name,int age) {
  10. this.name = name;
  11. this.age = age;
  12. this.studentID = id;
  13. }
  14. void study() {System.out.println("studing...");}
  15. }
  16. public class Inherit {
  17. public static void main(String[] args) {
  18. Student s1 = new Student(1,"Malongshuai",23);
  19. System.out.println(s1.studentID+","+s1.name+","+s1.age);
  20. s1.eat();
  21. s1.sleep();
  22. s1.study();
  23. }
  24. }

注:若您觉得这篇文章还不错请点击右下角推荐,您的支持能激发作者更大的写作热情,非常感谢!

java面向对象基础(一)的更多相关文章

  1. 【重走Android之路】【Java面向对象基础(三)】面向对象思想

    [重走Android之路][基础篇(三)][Java面向对象基础]面向对象思想   1 面向对象的WWH   1.1 What--什么是面向对象         首先,要理解“对象”.在Thinkin ...

  2. 【重走Android之路】【Java面向对象基础(二)】细说String、StringBuffer和StringBuilder

    [重走Android之路][基础篇(二)][Java面向对象基础]细说String.StringBuffer和StringBuilder   1.String String是Java中的一个final ...

  3. 【重走Android之路】【Java面向对象基础(一)】数据类型与运算符

    [重走Android之路][基础篇(一)][Java面向对象基础]数据类型与运算符   1.数据类型介绍 在Java中,数据类型分为两种:基本数据类型和引用类型. 基本数据类型共8种,见下表: 基本数 ...

  4. 086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结

    086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结 本文知识点:面向对象基础(类和对象)总结 说明 ...

  5. 085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用

    085 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 04 构造方法调用 本文知识点:构造方法调用 说明:因为时间紧张,本人写博客过程中只是 ...

  6. 084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字

    084 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 03 构造方法-this关键字 本文知识点:构造方法-this关键字 说明:因为时间紧 ...

  7. 083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法

    083 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 02 构造方法-带参构造方法 本文知识点:构造方法-带参构造方法 说明:因为时间紧张, ...

  8. 082 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 01 构造方法-无参构造方法

    082 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 02 构造方法介绍 01 构造方法-无参构造方法 本文知识点:构造方法-无参构造方法 说明:因为时间紧张, ...

  9. 081 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 06 new关键字

    081 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 06 new关键字 本文知识点:new关键字 说明:因为时间紧张,本人写博客过程中只是 ...

  10. 080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则

    080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则 本文知识点:单一职责原则 说明:因为时间紧张,本人写博客过程中只是 ...

随机推荐

  1. 阿里深度兴趣网络模型paper学习

    论文地址:Deep Interest Network for Click-Through Rate ... 这篇论文来自阿里妈妈的精准定向检索及基础算法团队.文章提出的Deep Interest Ne ...

  2. nodejs 做后台的一个完整业务整理

    大家知道js现在不仅仅可以写前端界面而且可以写后端的业务了,这样js就可以写一个全栈的项目.这里介绍一个nodejs + express + mongodb + bootstap 的全栈项目. 1.安 ...

  3. java_web学习(十一) 层的概念与应用

    一个项目通常分为三层: 所谓三层是表述层(WEB层).业务逻辑层(Business Logic),以及数据访问层(Data Access). ·WEB层:包含JSP和Servlet等与WEB相关的内容 ...

  4. golang 在 windows 下编译出 linux 二进制可执行文件的软件套装合集 [go 1.7.3环境]

    golang 很好用,不过要把工具链弄完整. 要不你会发现怎么不能编译跨平台的呀? 怎么写代码没提示啊? ... 这一整套弄下来并不容易. 所以精心准备了一套工具方便大家使用. 软件列表如图. 安装顺 ...

  5. 前端构建工具gulp之基本介绍

    1.基本介绍 gulp.js是一个自动化构建工具,是自动化项目的构建利器.可以对网站的资源进行优化,将开发过程中一些重复的任务通过执行命令自动完成.这样能很大的提高我们的工作效率. gulp.js是基 ...

  6. 在mac下使用终端命令通过ssh协议连接远程linux系统,代替windows的putty

    指令:ssh username@server.address.com 事例:wangmingdeMacBook-Pro:~ xxxxxxxxxx$ ssh root@XXXX.net The auth ...

  7. 记一次python的一些参数

    isdigit把字符串转化为整数 用法 if xx.isdigit(): xx=int(xx) index创建一个小原组,用法print(a.index(b),b)从0开始

  8. 让两个数x,y一直保持互质的模版

    int gcd(int x,int y) { )return x; else return gcd(y,x%y); }

  9. isupper()函数

    isupper()函数可以用来判断字符c是否为大写英文字母! 原型:extern int isupper(int c); 头文件:ctype.h 功能:判断字符c是否为大写英文字母 说明:当参数c为大 ...

  10. [bzoj2638] 黑白染色

    比较蛋疼的是我们可以先染个底色,再在底色上染别的东西. 由ccz大爷的题解可得..将目标状态里相同颜色的联通块缩点后,枚举起点,生成树里的最大节点深度就是需要的次数了, 如果最大深度是白色的话记得-1 ...