Java编程拾遗『对象和类』,java编程拾遗
Java编程拾遗『对象和类』,java编程拾遗
要讲Java中对象和类,Java面向对象的特性是不可避免的,Java中的对象和类其实就来自面向对象的编程思想。在之前的文章Java编程拾遗『Java概述』中,简述了Java面向对象的特性,本篇文章将重新介绍一下Java面向对象的编程思想及Java中对象和类的一些概念和使用。
1. 面向对象思想
1.1 面向对象 VS 面向过程
面向对象是一种编程思想,是当今软件开发方法中的主流方法之一,它把数据及对数据的操作方法整合在一起,作为一个相互依存的整体,即对象。对同类食物抽象出其共性,即类,类中的大多数数据,只能被本类的方法进行处理,类通过外部接口和外界发生关系。比如,站在抽象的角度,人具有身高、年龄、体重、血型等特征,同时具有会劳动、会直立行走、会吃饭等这些方法,人仅仅是一个抽象的概念,并不是一个存在的实体,所有具备人这个群体的属性与方法的对象都可以叫人。这个对象是实际存在的实体,每个人实体都是人这个群体的一个对象。
面向过程是一种一时间为中心的开发方法,就是自上到下的顺序执行,逐步求精,其程序结构是按照功能划分成若干个基本模块,这些基本模块形成一个属性结构,各模块之间的关系也非常简单,在功能上相对独立,每一个模块内部一般都是由顺序、选择和循环三种基本结构组成。面向过程的模块化是由子程序实现的,程序流程在写程序时就已经决定了。以五子棋为例,面向过程的设计思路就是首先分析问题的步骤:第一步,开始游戏。第二步,黑子先走。第三步,绘制画面。第四步,判断输赢。第五步,白子走棋。第六步,绘制画面。第七步,判断输赢。第八步,返回第二步。第九步,输出最后结果。如果把上面每个步骤都分别用一个函数来实现,就是一个面向过程的开发方法。
1.2 面向对象和面向过程的不同点
1.3 面向对象的特征
面向对象主要包括抽象、继承、封装、多态四个特征,这里简单讲一下。
1.4 面向对象的优点
2. 类
上面讲述了面向对象开发思想的一些概念,Java中面向对象实现的载体就是类,类其实就是对于现实事物抽象的封装,并且Java语言赋予了类继承、多态等面向对象的特性。Java中类的基本格式:
[类定义修饰符] class <类名> {
[类变量声明]
[成员变量声明]
[构造函数]
[类方法]
[成员方法]
[成员变量的get/set方法]
}
- 类定义修饰符:类定义修饰符定义了类的访问权限,可以省略,也可以是public, protected, private, static, final,其中public、protected , private三个最多只能出现其中之一,static和final可一起出现。
- public:公有权限,该类在任何地方都可访问。
- protected:受保护权限,只允许修饰内部类,表示在子类和本包中可见
- private:私有权限,只允许修饰内部类,表示只在本类中可见
- 缺省:包访问权限,同一package可用
- static:静态类,如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,程序会编译都不会过。
- final:修饰class时,表示类为不可变类,类的对象一旦被创建就不能被修改了,比如String类。
- 类名:类名使用UpperCamelCase风格。
- 类变量:static修饰的变量,变量为类持有,所有对象共享该变量。
- 实例变量:非static修饰的变量,变量为对象持有,每个对象持有一个该成员变量。
- 构造函数:构造对象实例的方法。
- 类方法:static修饰的方法,也叫静态方法。属于整个类的,不是属于某个实例的,只能处理static域或调用static方法。
- 实例方法:非static修饰的方法,属于对象的方法,由对象来调用。
2.1 类定义修饰符
上面讲过public, protected, private, static, final都可以用来修饰class,其中public、protected , private三个最多只能出现其中之一,static和final可一起出现,下面分别讲一下这几种修饰符。
2.1.1 public
public权限表示共有权限,时日常开发中最常见也是最简单的一种权限。因为时公有权限,所以在任何地方使用都不受限制,这里不多解释了。
2.1.2 protected
受保护权限,在修饰class时只允许修饰内部类,表示在子类和本包中可见。这里要注意的时,不是本包下,protected内部类不可见,即使是子类也不行。
package com.zhuoli.service.thinking.java.clazz;
@Getter
@Setter
public class ProtectedClass {
private String name;
private Integer age;
protected void print() {
System.out.println("protected method");
}
@Getter
@Setter
protected class InnerProtectedClass {
private String innerName;
}
@Test
public void test() {
//protected内部类在本类中可见
InnerProtectedClass innerProtectedClass = new InnerProtectedClass();
innerProtectedClass.setInnerName("k8s");
System.out.println(innerProtectedClass.getInnerName());
}
}
package com.zhuoli.service.thinking.java.clazz.other;
public class ProtectedClassTest3Sub extends ProtectedClass {
@Test
public void test(){
//父类protected方法子类可见,无论子类是不是与父类在同一个包下
print();
//子类与父类不在同一个包下,子类实例可以访问其从基类继承而来的protected方法
ProtectedClassTest3Sub protectedClassTest3Sub = new ProtectedClassTest3Sub();
protectedClassTest3Sub.print();
//子类与父类不在同一个包下,则父类实例不能访问父类的protected方法,以下编译报错
ProtectedClass protectedClass = new ProtectedClass();
protectedClass.print();
//注意protected内部类的继承规则跟protected成员方法略有不同,protected内部类严格遵守同包和子类可见条件,非同包即使时子类也不可见,以下报错
InnerProtectedClass innerProtectedClass1 = new InnerProtectedClass();
//这种方式也不行,因为不同包
ProtectedClassTest3Sub protectedClassTest3Sub1 = new ProtectedClassTest3Sub();
ProtectedClassTest3Sub.InnerProtectedClass innerProtectedClass2 = protectedClassTest3Sub1.new InnerProtectedClass();
}
}
以上可以看出,父类protected内部类,访问权限是本包、子类,但是子类必须与父类在同一个包下,否则也是不可见的。而对于父类protected成员,访问权限也是本包及子类,但是不要求子类必须与父类在同一包下,若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,基类实例不能访问父类的protected方法。
2.1.3 private
跟protected一样,在修饰class时,只允许修饰内部类,表示只在本类中可见。对于可见性没什么好讲的,只允许在本类中出现。这里借助private内部类讲一下使用内部类使用的一个好处——实现多重继承。在Java中是不允许多重继承的,但是可以使用内部类变相实现多重继承。
public class Father {
public int strong(){
return 9;
}
}
public class Mother {
public int kind(){
return 8;
}
}
如上存在两个类Father和Mother类,对于一个子类Son,如果想同时继承这两个类,显然是不可以的,但是可以通过内部类实现一种折中的多重继承,如下:
public class Son {
private class Father_1 extends Father{
public int strong(){
return super.strong() + 1;
}
}
private class Mother_1 extends Mother{
public int kind(){
return super.kind() - 2;
}
}
public int getStrong(){
return new Father_1().strong();
}
public int getKind(){
return new Mother_1().kind();
}
public static void main(String[] args) {
Son son = new Son();
System.out.println("Son's Strong:" + son.getStrong());
System.out.println("Son's kind:" + son.getKind());
}
}
这一点在《Thinking in Java》中有讲到,有兴趣的同学可以取了解一下。
2.1.4 缺省
缺省表明权限是包访问权限,只在本包内可访问。
package com.zhuoli.service.thinking.java.clazz.default0;
@Getter
@Setter
class DefaultClass {
private String name;
private Integer age;
void print() {
System.out.println("protected method");
}
@Getter
@Setter
class InnerDefaultClass {
private String innerName;
}
}
package com.zhuoli.service.thinking.java.clazz.default0;
public class DefaultClassTest {
DefaultClass defaultClass = new DefaultClass();
//同包中可见
private DefaultClass.InnerDefaultClass innerDefaultClass = defaultClass.new InnerDefaultClass();
@Test
public void test(){
innerDefaultClass.setInnerName("inner class");
System.out.println(innerDefaultClass.getInnerName());
//包访问权限,父类实例可以在同包下调用自己的protected方法
defaultClass.print();
}
}
package com.zhuoli.service.thinking.java.clazz.default0.other;
public class OtherPackageTest {
@Test
public void test() {
/*DefaultClass是包访问权限,在不同包下不可见,以下编译报错*/
DefaultClass defaultClass = new DefaultClass();
}
}
本包外,DefaultClass都是不可见的。其实Java看以上三种访问权限可以发现,public和private最干净,一个是不受限,另一个只在本类中可见。protected在修饰类时,只能修饰内部类,表明类权限是子类可见及同包可见,通过上述示例代码可以发现,protected内部类的可见性其实就是在本包内(子类可见的前提也是子类父类在同一个包下)。类定义修饰符缺省的情况下,类只能在本包中可见。其实通常讲public、protected、default、private修饰权限其实都是在讲修饰成员变量时,成员变量的可见性,权限如下表:
当前类 | 同一package | 子类 | 其它package | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
对于protected权限,这里单独讲一下其他package的说明,如果子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,父类实例不能访问其protected方法,也就是说其它package不可见不是绝对的。对于protected访问权限,可以参考这篇文章,讲的很清楚。
2.1.5 static
在Java世界里,经常被提到静态这个概念,static作为静态成员变量和成员函数的修饰符,意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见。如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,如果外部类被定义为static,则会编译报错。Oracle官方给出了内部类和静态内部类的区别:
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
从字面上看,一个被称为静态嵌套类,一个被称为内部类。嵌套的意思,是可以完全独立存在意思,只是想借用外部类的壳用一下,隐藏一下自己。它是一个独立的类,完全是形式上的“内部”,和外部类没有本质上“内外”的关系,如下:
静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计,一个常见的用法就是通过静态内部类实现builder模式:
public class Outer {
private String name;
private int age;
public static class Builder {
private String name;
private int age;
public Builder() {
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withAge(int age) {
this.age = age;
return this;
}
public Outer build() {
return new Outer(this);
}
}
private Outer(Builder b) {
this.age = b.age;
this.name = b.name;
}
}
静态内部类调用外部类的构造函数,来构造外部类,由于静态内部类可以被单独初始化,所以可以如下调用:
Outer outer = new Outer.Builder().withName("zhuoli").withAge(18).build();
return outer;
2.1.6 final
当一个类被final修饰时,表明此类是个不可变类,不能被继承,所有的方法都不能被重写。但这并不表示final类的成员变量也是不可改变的,要想做到final类的成员变量不可改变,必须给成员变量增加final修饰。
2.2 类名
《码出高效 阿里巴巴Java开发手册》中讲到,类名使用 UpperCamelCase 风格,但以下情形例外:DO/BO/DTO/VO/AO/PO/UID 等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
2.3 类变量
类变量也叫静态变量,是指类中被static修饰的成员变量,实质上就是一个全局变量。如果某个内容是被所有对象所共享,那么该内容就应该用static修饰,没有被static修饰的内容,其实是属于对象的特殊描述。类变量在类加载时,在堆中分配内存,类变量的生命周期一直持续到类卸载,并且被所有对象所共享。
2.4 实例变量
实例变量也叫非static成员变量,是指类中没有被static修饰的成员变量,用于描述对象特征。实例变量是属于特定的对象的,每个对象持有的实例变量都有可能是不同的,在创建对象时在堆上分配内存,当对象被回收时,实例变量的生命周期也相应结束。
2.5 构造函数
构造函数是一种特殊的函数,用来在对象实例化时初始化对象的成员变量。在Java中,构造函数具有以下特点:
2.6 类方法
类方法也叫静态方法,是指通过static修饰的成员方法,用于描述类行为,在调用方式上可以直接使用类名调用,而不用通过实例调用。在同一个类中,实例方法内部可以直接使用静态方法,但是静态方法中则无法直接调用这个实例方法,相应的静态方法也只能访问静态成员。关于静态方法,相信很多人都有一种熟悉又陌生的感觉,下面简单讲一下关于静态方法和非静态方法的一些误解:
那么为什么还要区分静态方法和实例化方法 ?
如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是“静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。
2.7 实例方法
实例方法就是类中那些没有被static修饰的方法,属于对象行为,在讲静态方法时,已经讲了静态方法和实例方法的区别,这里不多阐述了。
3. 基础概念答疑
当涉及到类和概念时,总有一些基础概念,当遇到时感觉很简单,但要表述时往往又不能完全表述清楚,这里总结以下一些基本概念。
3.1 继承
继承是面向对象的一个重要特性,通过继承,子类可以使用父类中的一些成员变量和方法,从而提高代码的复用性,提高开发效率。Java中继承主要有以下几个特性:
3.2 类之间的关系
Java中,类之间最常见的关系有以下三种:
- 依赖(“uses-a”)
- 聚合(“has-a”)
- 继承(“is-a”)
依赖(dependence),即”uses-a”关系,是一种最明显、最常见的关系。如下:
public class Order{
public OrderVO getOrder(GetOrderRequest request) {
Account account = AccountService.getAccount(**);
……
}
}
Order类中使用了Account类,这时候Order类与Account就是依赖关系(“uses-a”)。开发中应该该尽可能地将相互依赖地类减至最少。如果类A不知道B地存在,它就不会关心类B地任何改变(类B地改变不会导致A产生任何bug)。用途软件工程地属于来说,就是让类之间地耦合度最小。
聚合(aggregation),即”has-a”关系,是一种具体且易于理解的关系。如下:
public class Order{
private Account account;
public Order(Account account){
this.account = account;
}
public OrderVO getOrder(GetOrderRequest request) {
Long accountNo = account.getAccountNo();
……
}
}
Order类中持有了一个Account对象,这时候Order类和Account类就是”has-a”的关系。
继承(inheritance),即”is-a”关系,是一种表示特殊与一般的关系,其实即时父类与子类的关系。如下:
public class Order{
private Long orderNo;
private Long price;
//getter and setter
}
public class Xorder extends Order{
……
}
Xorder类继承了Order类,Xorder类与Order类就是”is-a”关系。
继承和组合是开发中最常见的两种代码常用方式,在开发中,一般遵循以下规则:
3.3 多态
多态是面向对象程序设计中代码重用的一个重要机制,他表示当一个操作作用在不同的对象时,会有不同的语义,从而产生不同的结果。在Java中,多态主要有以下两种实现方式:
重载:
覆盖:
参考链接:
相关文章
- 暂无相关文章
用户点评