写点什么

Java 面向对象

作者:andy
  • 2022-10-28
    北京
  • 本文字数:13083 字

    阅读完需:约 43 分钟

面向对象

在计算机语言开发的历史长河中,先后出现了面向过程和面向对象的编程思想。由于面向过程过于复杂,代码不可重复使用,故 IBM 提出了一切皆对象的理念,产生了面向对象思想。

面向对象思想于 20 世纪 70 年代由 IBM 提出的,通过 smalltalk 语言进行推广。C 语言借鉴了面向对象思想之后,产生了 C++语言。随后,由于 C++需要通过指针进行内存分配,对于开发者而言,是一项非常繁重的事情。因此,在 C++的基础上进行改进,从而产生了 Java 语言。

面向过程主要是针对某一具体的问题所采取的解决方案,为特定功能服务。而面向对象则是针对某类问题所采取的解决方案,组件化的形式便是面向对象的很好体现。面向对象思想涉及软件开发的各个方面,包括面向对象分析 OOA、面向对象设计 OOD、面向对象编程 OOP。

面向对象具有以下特征:

封装性:保护内部结构的安全性,隐藏内部结构实现;

继承性:已有结构的基础上扩展新的功能,复用已有功能;

多态性:由继承而产生的相关的不同的类,其对象针对同一消息做出的不同响应。方法的重载和覆写,父类与子类对象之间的相互转换,数据类型之间的相互转换,都属于多态性的表现。


类与对象

“一切皆对象”,基于类与对象的概念,才有了面向对象思想的产生。

类,也就是共性,是对具有相同特征的所有事物的高度抽象。对象,则是单一的具体事物,是个性的体现。

在 Java 中,类为引用数据类型,需要进行内存管理。因此,在进行对象实例化时,需要使用关键字 new 进行堆内存(伊甸园或者老年代)空间的开辟。同时,对于实例化对象,堆内存空间的指向可能没有变量名(匿名对象),也可能有一个变量名,或者多个变量名(引用传递)。

类和对象的核心知识概念如下:

类和的定义、对象声明、关键字 new 实例化对象、属性访问、引用传递。其中,对象声明和实例化对象,涉及栈内存和堆内存中的伊甸园或者老年代的开辟

代码示例:


package org.fuys.ownutil.encapsulation;
class ID { String name;}
public class ClassAndObjeceInstance { public static void main(String[] args) { ID id = new ID(); id.name = "andy fu"; ID andy = id; }}
复制代码


封装性

当类的属性没有进行封装保护,其它外部类和对象是可以直接访问类的内部属性,也就可以直接属性进行修改,这样是不安全的。

为了不让外部直接访问类的内部属性,保护类的内部结构,通过封装性隐藏类的内部结构。在代码中,使用 private、default、protected 关键字进行限定,具体选择根据访问权限设置,一般使用 private。

为了能够在实例化对象时就进行属性赋值,Java 提供了构造方法。构造方法的主要作用就是实例化对象时,设置属性的初始化内容

如果类中未定义构造方法,则 Java 编译代码时会自动生成默认无参构造方法。但是,一旦类中定义了构造方法,则不会再自动生成默认无参构造方法

通过构造方法可以实例化对象,那么,当没有进行对象声明时,便产生了匿名对象。

封装性的核心概念如下:

封装性保护类的内部结构、使用关键字 private 修饰属性、通过构造方法对对象属性进行初始化赋值、匿名对象

代码示例:


package org.fuys.ownutil.encapsulation;class ID {	private String name;	public void setName(String n){		name = n;	}	public String getName(){		return name;	}	public ID(String n){		name = n;	}}public class ClassAndObjeceInstance {	public static void main(String[] args) {		ID id = new ID("andy fu");		new ID("jane yang");		System.out.println(id.getName());	}}
复制代码


数组

引用数据类型除了类,还包括数组。在整个计算机发展过程中,数组是一个重要的概念。数组是一系列值的集合。

既然数组是引用数据类型,其实例化对象有动态实现和静态实现。数组通过索引进行值的变更。数组有一个最大局限,就是数组的长度是固定的,不能动态变更。因此,为了解决数组不能动态扩展长度的问题,Java 提供了链表和类集框架。

数组中的内容,可以是基本数据类型,也可以是引用数据类型。当为引用数据类型是,则数组为对象数组。

这里需要注意二维数组,二维数组是多行一维数组的集合,因此,会出现多行中的数组个数不相等的情况,而对于这些空出来的空间,没有任何值,也没有内存空间。

代码示例:


int a[][] = new int [][]{{1,2,3,4},{5,6,7},{8,9},{1,2,3,4,5,6,7,8,9}};for(int x=0;x<a.length;x++){	for(int y=0;y<a[x].length;y++){		System.out.print(a[x][y]+"\t");	}	System.out.println();}
复制代码


对于数组而言,实际开发中不会用得太多,反而是在理解数据结构上,用得非常多,所以,对于算法的问题,数组中有经典的排序、转置算法,可以通过程序实现加深对于数据结构的理解。

Java 中也提供了对于数组的操作方法,常用的有数组拷贝(System.arraycopy())和数组排序(Arrays.sort())。其中 System.arraycopy()支持多维数组,但是,需要注意维度相同以及不要数组越界。Arrays.sort(),该方法只提供一维数组排序。

代码示例:







package org.fuys.ownutil.encapsulation;import java.util.Arrays;import java.util.Random;class Mouse{private String brand;public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Mouse(String brand) {super();this.brand = brand;}}public class ArrayInstance {public static void main(String[] args) {// 1 dimensional arraychar []chars = new char[]{'a','n','d','y'};System.out.println(chars);System.out.println(chars.length);chars = new char[2];for(int x=0;x<chars.length;x++){chars[x] = String.valueOf(new Random().nextInt(9)).charAt(0);System.out.println((int)chars[x]);}System.out.println(chars);// object arrayMouse[] mouses = new Mouse[]{new Mouse("logitech")};for(int x = 0;x<mouses.length;x++){System.out.println(mouses[x].getBrand());}// 2 dimensional arrayint[][] ints = new int[][]{{1,4},{3,2}};for(int x=0;x<ints.length;x++){for(int y=0;y<ints[x].length;y++){System.out.println(ints[x][y]);}}// array operate methodint [][] newInts = new int[2][2];System.arraycopy(ints, 0, newInts, 0, 2);for(int x=0;x<newInts.length;x++){for(int y=0;y<newInts[x].length;y++){System.out.println(newInts[x][y]);}}Arrays.sort(chars);System.out.println(chars);}}
复制代码


String 类

Java 中最为常用的引用数据类型,非 String 类莫属。字符串实例化对象有两种方式,直接赋值和构造方法。字符串常量是 String 类的匿名对象。字符串使用“"”英文双引号修饰。

在进行引用数据类型对象分析时,需要结合 JVM 内存空间进行。声明的变量存储在栈空间,实例化对象存储在堆空间。

JVM 底层存在一个对象池,可以实现资源共享。对于字符串而言,一旦开辟内存空间,就不能再修改,也就是不会再重复开辟新的堆内存空间,直接共享原有存储内容。Java 中 String 类使用 new 开辟新空间,这段空间是不能入对象池的,因此,可以使用 public String intern()进行入池,但是不建议这样使用。

示例如下:


String a = "yun";String b = new String(a);System.out.println(a==b); // falseString c = "yun";System.out.println(a==c); // trueSystem.out.println(b==c); // falseb.intern();String d = "yun";System.out.println(a==d); // trueSystem.out.println(b==d); // falseSystem.out.println(c==d); // trueString e = new String(a).intern();String f = "yun";System.out.println(e==b); // falseSystem.out.println(e==a); // trueSystem.out.println(e==f); // trueSystem.out.println(a==c&c==d&d==e&e==f); // trueSystem.out.println(a==b); // false
复制代码


注意:

1、String 类字符串一旦定义,就无法修改;

2、“String b = new String(a);”、“b.intern();”、“String e = new String(a).intern();”三者方法实际结果不同,需要小心。

正确代码示例如下:


String a = "Hello World!!!";String b = a;String c = new String(a);String d = new String(a);d.intern();String e = new String(a).intern();System.out.println(a == b);System.out.println(a == c);System.out.println(a == d);System.out.println(a == e);
复制代码


由于 String 类为引用数据类型,故会出现引用传递。

注意以下程序(目前为止还是想不明白):


String a = "m";String b = a + "n";String c = "mn";String d = "m"+"n";System.out.println(b==c); // falseSystem.out.println(b==d); // falseSystem.out.println(c==d); // true
复制代码


实际上 b 中的堆内存存的是引用,故地址与“mn”字符串不同。

String 类提供了许多的操作字符串的方法,实际开发中频繁使用。下面只是提醒一些容易忽略的方法,其他方法也很重要。

String 类中提供了 compareTo()进行两个字符串的大小判断,原因在于 String 类实现了 Comparable 接口。

使用 split()方法进行字符串拆分,如果正则表达式是空串,那么,字符串全都拆分。

注意以下另一个程序,涉及引用传递的问题:


public static void main(String[] args) {String str = "hello";change(str);System.out.println(str);}public static String change(String str){return str = "world";}
复制代码


this 关键字

为了能够在类中表明当前对象,更加明确具体地为当前对象赋值,所以,Java 中提供了 this 关键字。this 表示的是当前对象,可以访问类的属性和方法。this()表示无参构造函数,需要放置于方法体首行,不能与 super()一起使用。

代码示例:



package org.fuys.ownutil.encapsulation;class Keyboard{private String brand;public String getBrand() {return this.brand;}public void setBrand(String brand) {this.brand = brand;}public Keyboard() {super();}public Keyboard(String brand) {this();this.brand = brand;}public String toString() {return "Brand is " + this.brand;}}public class ThisInstance {public static void main(String[] args) {Keyboard keyboard = new Keyboard("logitech");System.out.println(keyboard.toString());}}
复制代码


引用

引用是 Java 中非常重要的概念,是实现类对象之间互相调用的工具。在这里不做过多的讲解,以下是人员、角色、权限组和权限的模型,这个是基础。许多的管理系统,都是在这样的模型下演化出来的,变也是在此基础上变



以上模型也是掌握由简单 Java 类演化出来的映射关系的基础。


对象比较

有了类与对象的概念之后,当对象数量不再是单一的一个,那么,多个对象之间如何进行比较呢?

基本数据类型使用“==”进行比较,字符串使用“equals()”进行比较,对象则是进行属性内容的比较。

Java 类中提供了许多的比较方法,通常方法名以 compare 为主。对于对象比较,其比较方法定义于该类中,比较的最初思路如下:

空的判断、对象地址的判断以及对象内容的判断。

以下展示实例代码模型


class Computer{private String name;private double price;public String getName() {return name;}public void setName(String name) {this.name = name;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public Computer() {super();}public Computer(String name, double price) {this();this.name = name;this.price = price;}public int compareTo(Computer computer){// 1,nullif(computer == null)return 1;// 2,addressif(this == computer)return 0;// 3,fieldsif(this.price > computer.price){return 1;}else if(this.price < computer.price){return -1;}else {return 0;}}}
复制代码


补充:鉴于 Java 类中的多态性,也就是 Object 类进行向上转型和向下转型时出现的问题,进行对象比较的最终思路为:

1、判断比较对象是否为空;

2、比较对象地址;

3、判断比较对象是否与被比较对象是同类关系;

4、比较内容。

对象比较修改后的比较方法代码如下:


public int compareTo(Object obj){// 1,nullif(obj == null)return 1;// 2,addressif(this == obj)return 0;// 3,instanceofif((obj instanceof Computer)==false){return -1;}Computer computer = (Computer) obj;// 4,fieldsif(this.price > computer.price){return 1;}else if(this.price < computer.price){return -1;}else {return 0;}}
复制代码


static 关键字

对于多个对象而言,进行同一资源的访问是不可回避的问题。不是所有的资源都是对象独享的,也会有共享资源。

为了实现共享资源,可以使用 static 关键字进行修饰,从而使得该类的所有对象都可以共享该资源。static 修饰的方法和属性,不能直接访问非 static 修饰的方法和属性,因为非 static 修饰的方法和属性,需要实例化对象之后才能访问。

许多的工具类,只需要访问静态方法和常量,类中的静态方法和常量都是用 static 修饰,故不需要通过实例化对象进行访问,直接通过类进行访问。

需要注意在 JVM 中,对于 static 修饰的资源,存储在全局数据区,也可以说是方法区。

注意:使用 static 修饰的变量不能再方法体中使用,不能起到全局变量的作用。放置于方法体内,就相当于变为了局部变量,这是不合理的


代码示例:


package org.fuys.ownutil.util;import java.io.File;import java.math.BigDecimal;public class FileUtil {public static final int BYTE = 0;public static final int KILOBYTE = 3;public static final int MEGEBYTE = 5;public static final int GIGABYTE = 7;/** * file length *  * @param filePath * @param scale * @param module * @return */public static BigDecimal length(String filePath, int scale, int module) {File file = new File(filePath);BigDecimal bg = null;if (file.exists() && file.isFile()) {bg = new BigDecimal(file.length());BigDecimal divisor = new BigDecimal(1024);switch (module) {case GIGABYTE:bg = bg.divide(divisor).divide(divisor).divide(divisor);break;case MEGEBYTE:bg = bg.divide(divisor).divide(divisor);break;case KILOBYTE:bg = bg.divide(divisor);break;case BYTE:break;default:break;}bg = bg.divide(new BigDecimal(1), scale, BigDecimal.ROUND_HALF_UP);}return bg;}}
复制代码


代码块

类是由代码组成的,那么,一堆代码的集合,就叫做代码块。代码块由{}限定,根据代码位置、static、synchronized 划分,分为普通代码块、构造代码块、静态代码块、同步代码块。需要注意的是代码块与方法体是不同的概念,虽然两者都以{}进行边界界定。

在实际开发中,代码块较少使用。

代码示例如下:


/** * Code Block instance * @author ys * */class CodeBlock{// Static Code Block// Higher privilege than Constructor Code Block// Used first time when class is usedstatic{System.out.println("Static Code Block");}// Constructor Code Block// Higher privilege than Constructor Method// Used many times when object is instantiated{System.out.println("Constructor Code Block");}public CodeBlock(){System.out.println("Constructor Method");}public void normalCodeBlock(){// Normal Code Block{String info = "Normal Code Block";System.out.println(info);}String info = "Normal Code";System.out.println(info);// Synchronized Code Blocksynchronized(this){info = "Synchronized Code Block";System.out.println(info);}}}
复制代码


内部类

对于类的结构而言,通常由属性和方法组成。但是,类的内部同样可以由类组成。

一个类的内部再编写一个类,也就是内部类。内部类的一个特点就是方便访问外部类的私有属性,但是,外部类不能直接访问内部类的属性,需要通过内部类对象访问

外部类.this”表示的是外部类的本类对象。内部类的对象不可能离开外部类的对象存在,一定要有外部类对象,才能有内部类对象。当然,这是在内部类没有被 static 修饰的情况下。

内部类的实例化分两种:

内部类不被 static 修饰:外部类.内部类 内部类对象 = new 外部类().new 内部类()

内部类被 static 修饰:外部类.内部类 内部类对象 = new 外部类.内部类()

如果内部类只希望被外部类访问,而不能被外部访问,可以使用 private 修饰,链表和类集框架便是用到了这一原理

外部类方法中定义了内部类,内部类可以直接访问外部类属性,但是对于内部类访问方法体中定义的参数和变量,则会有所区别。jdk1.7 之前,参数和变量需要加上 final 进行修饰,jdk1.8 之后,则不需要使用 final 进行修饰。

经过大量的内部类使用,可以说,内部类是一个“毁三观”的事情,改变了类和接口的内部结构。

代码示例:


package org.fuys.ownutil.encapsulation;/** * Instance of Inner class of outer class  * @author ys * */class Outer {// static code blockstatic{outerStaticField = "Hello World!!!";}// private fieldprivate String outerField;// static private fieldprivate static String outerStaticField;// constructor methodpublic Outer(String outerField){this.outerField = outerField;}// normal inner classclass Inner1{public void getOuterField(){// access outer class fieldSystem.out.println(Outer.this.outerField);}}// private inner classprivate class Inner2{private String innerField;public Inner2(){}public Inner2(String innerField){this();this.innerField = innerField;}public void getOuterField(){System.out.println(Outer.this.outerField);}}// static inner classstatic class Inner3{public void getOuterField(){System.out.println(Outer.outerStaticField);}}public void getOuterField(){new Inner2().getOuterField();}public static void defineMethodInnerClass(String outerField){class Inner{public void getOuterField(){System.out.println(outerField);}}new Inner().getOuterField();}public void printInner2Field(){System.out.println(new Inner2("Inner2 field").innerField);}}public class OuterInnerModel{public static void main(String[] args) {new Outer("Inner1").new Inner1().getOuterField();new Outer("Inner2").getOuterField();new Outer("Inner2Field").printInner2Field();new Outer.Inner3().getOuterField();Outer.defineMethodInnerClass("MethodInnerClass");}}
复制代码


链表

由于数组长度的局限性,可以通过链表实现数组的动态扩展。类集框架的原理便是来源于链表。同样,引用传递在链表中也得到了充分的使用。

为了方便理解链表,可以用火车的每节车厢作为实例去理解。对于每节车厢而言,只需要记住下一节车厢即可,至于那个车厢作为下一个则有更高级别领导去分配。由此也可以理解,各自的分工不同。就相当于领导与下属之间的关系,下属只负责把自己的事情做好,而领导则是需要为各个下属分配不同的工作内容。链表也是这样的道理,每一个节点,只需要记录好下一个节点,而链表则是分配好各个节点之间的关系。

先从简易的模式理解链表,定义好根节点,由根节点作为链表操作的起点,从而实现单向链表。对于各个节点之间的关系,我们先忽略,先把各个节点对象创建出来。然后,再由链表设置对象之间的关系。

链表的核心作用:

客户端调用不用关注如何实现,只需要使用好链表即可;

链表负责控制根节点和各个节点间的关系;

节点负责保存数据和引用。

基于节点负责保存数据和引用、链表负责控制根节点和各个节点的关系,为了不让外部可以操作链表里的节点数据,故将节点进行封装为内部类。需要注意一大特性,内部类和外部类互相可以直接访问属性。

链表涉及的知识点很多,包含类的分工、方法定义(增、删、改、查)、内部类、全局变量,需要重点小心。其中,方法的定义,按照类集的标准来设置。对于数据结构,链表也是加强对于数据结构的理解。

在进行链表代码结构设计中,不适合直接进行输出,而是要转换为相应数据类型的对象数组,交由客户端进行输出。


继承性

继承性,就是在已有代码结构的基础上扩展功能,其主要目的在于解决代码重用的问题。

使用关键字 extends 实现,定义格式如下:

class 子类 extends 父类 {}

子类又称为派生类,父类称为基类、超类。

对于继承而言,只能有一个父类,也就是单继承。但是为了能够实现多继承,可以使用多层继承的方式,或者接口的方式实现。

子类继承父类的时候,会继承父类的全部属性和全部方法。但是对于父类中的私有属性,只能隐式继承,对于隐式继承的私有属性,只能通过继承父类的非私有方法进行访问。非私有操作可以显示继承。

注意:

1、先有父类对象,才能有子类对象;

2、构造方法实例化对象,会调用父类构造,子类构造方法没有编写父类构造方法,则会默认调用父类无参构造。如果,一旦父类没有定义无参构造方法,会无法编译通过;

3、建议编写类结构时,增加无参构造,目的就在于子类继承的时候,可以调用父类无参构造方法,从而为子类实例化服务。


覆写

对于子类重新编写相同的属性和方法,则是属性覆盖和方法覆写。被子类覆盖的属性和覆写的方法,不能拥有比父类更为严格的限制。方法的覆写是一个容易造成陷进的地方,需要多加小心。

为了能够明确的由子类调用父类中的“覆写”方法,使用 super.方法()进行访问,避免覆写父类方法时所产生的递归调用问题。

this 表示当前类的实例化对象,super 表示当前对象的父类,而不是父类对象,只有 “super.” 才表示父类对象,故 super 不能单独使用

继承性和覆写的代码示例如下:


package org.fuys.ownutil.inheritance;
import java.util.Date;
/** * Notice difference between extending class relationship * and simple java class implementing relationship different tables * @author ys */class SuperClass{static{honor = "NORMAL";FAMILYCREATETIME = new Date();}public final static Date FAMILYCREATETIME;private String name;private static String honor;public SuperClass(){}public SuperClass(String name){this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public static String getHonor() {return honor;}public static void setHonor(String honor) {SuperClass.honor = honor;}public String getInformation(){return "Honor --> " + honor + " | Name --> " + this.name + " | Super";}void print(){System.out.println(this.getInformation());}}class SubClass extends SuperClass{public SubClass(){}public SubClass(String name){// illegal syntax --> super *****************************super();this.setName(name);}public String getInformation(){return "Honor --> " + getHonor() + " | Name --> " + this.getName() + " | Sub";}protected void print(){// System.out.println(this.getInformation());super.print();// single use of super is wrong code convention// System.out.println(super);// super with . means super instance object}}class SubSubClass extends SubClass{public SubSubClass(String name){this.setName(name);}public String getInformation(){return "Honor --> " + getHonor() + " | Name --> " + this.getName() + " | Subsub";}/* * Incompatible print comparing with same method from superClass. * Because of return value type.public boolean print(){System.out.println(this.getInformation());return true;} */}public class ExtendsInstance {public static void main(String[] args) {SubSubClass subSub = new SubSubClass("jane");subSub.print();SubClass sub = new SubClass("andy");SubClass.setHonor("UP");sub.print();SuperClass sup = new SuperClass("huangdi");sup.print();System.out.println(SubSubClass.FAMILYCREATETIME);System.out.println(SubClass.FAMILYCREATETIME);System.out.println(SuperClass.FAMILYCREATETIME);}}
复制代码


final

类之间可以通过继承实现上下层关系,但是有时候是不需要子类继承父类的,故可通过关键字 final 实现。

使用关键 final 定义修饰的类、方法、变量,目的在于保护对应的类、方法、变量不被破坏修改。

注意以下三点即可:

final 修饰的类,即最终类,不可被子类继承,该类俗称太监类;

final 修饰的方法,不能被子类覆写;

final 修饰的变量,叫做最终变量,也叫常量,一旦定义就需要赋予初始值,并且不可被修改,标识符应该全部大写。对于全局常量,使用 public static final 定义。

补充:在 jdk1.0 时,并没有完全的定义常量名的命名规则,因此出现了用小写表示常量的情况,为了让老版本程序在新版本下能够继续执行,所以,就没有变更。java.io.File 类中的分隔符常量,就是这样的问题的体现。分割符常量:public static final String separator


多态性

多态性有以下体现:

方法的多态性

方法的重载:在同一个类中,同一个方法名,通过不同的参数个数和参数类型实现;

方法的覆写:在子类与父类关系中,同一个方法名,子类对父类方法的功能进行扩展;

对象的多态性:在类的继承关系中,子类与父类对象的相互转换。

向上转型(自动转型):父类 父类对象名 = 子类实例化对象;

向下转型(强制转型):子类 子类对象名 = (子类)父类实例化对象;

向上转型(开发中 80%使用),子类与父类的参数个数和类型统一,方便程序设计。

向下转型(开发中 5%使用),需要进行子类的特殊功能设计时使用。

注意:

1、开发中 15%不需要使用转型,尤其是一些系统类,如 String;只有发生了向上转型,才能发生向下转型。

2、当子类向上转型,父类对象想要调用子类定义的特殊功能方法时,是无法编译通过,所以,也才需要进行向下转型,这也是为什么类集中不用 ArrayList 去实例化 Collection 接口;

3、各个数据类型之间的相互转换,从大概念理解,也符合多态性。尤其是有了包装类之后,基本数据类型与字符串之间的转换。


抽象类

普通类中的方法需要有方法体,但是,对于类而言,如果想要实现只是定义,但是,不想要具体代码实现,真正的代码实现交给子类完成。这就提出了抽象方法的概念,由抽象方法得到抽象类。

抽象类是由抽象方法组成的类。抽象方法是没有方法体“{}”的方法,抽象方法和抽象类使用关键字 abstract 定义。

抽象类的相关使用原则:

1、抽象类除了抽象方法,类结构与普通类相同。故抽象类也有属性,也有构造方法,通过构造方法对属性进行初始化。

2、外部抽象类不能使用 static 修饰,内部抽象类可以使用 static 修饰,相当于一个外部抽象类,进行子类继承时,使用“外部抽象类.内部抽象类”形式。


abstract class A {static abstract class B {public abstract void print();}}class C extends A.B {@Overridepublic void print() {System.out.println("C");}}
复制代码


3、抽象类不能使用关键字 final 修饰,因为无法子类进行继承。

4、抽象类包含抽象方法,不能进行实例化,需要通过子类进行向上转型得到实例化对象;

5、抽象类没有包含抽象方法,但是使用 abstract 关键字修饰,依然是抽象类。

6、对于抽象类中的 static 修饰的方法,依然可以直接类直接访问,因为静态方法不需要对象访问。


abstract class D{public static void print(){System.out.println("D");}}
复制代码


7、抽象类定义一个特定的系统子类,可以避免外部子类操作。许多系统的类库就是使用这样的方式隐藏子类,一般使用静态方法,方法名为 getInstance()。Calendar 类便采用这样的方法。


abstract class F{private static class G extends F{@Overridepublic void print() {System.out.println("G");}}public abstract void print();public static F getInstance(){return new G();}}
复制代码


8、抽象类的子类必须覆写抽象方法;

9、抽象类的子类实例化,一定是先执行父类构造方法,再执行本类构造方法,即先有父类对象,再有子类对象。

10、对于普通类而言,尽量不要继承普通类,20%的情况下继承抽象类,80%的情况下实现接口,这也是因为抽象类单继承的局限,避免多层继承;

11、抽象类的主要应用就是模板设计模式

12、在任何类执行构造方法之前,所有属性的值都是系统的默认值。子类构造方法之前,执行父类构造方法,子类属性中的值没有进行赋值,父类构造执行,调用抽象方法,因为抽象方法没有方法体,故调用子类覆写的抽象方法。


abstract class H{public H(){this.print();}public abstract void print();}class I extends H{private String info = "HELLO";public I(String info){this.info = info;}@Overridepublic void print() {System.out.println(this.info);}}
复制代码


*在 Java 中 Calendar 类便是以上方式的实际应用。

public abstract class Calendar

extends Object

implements Serializable, Cloneable, Comparable

方法

public static Calendar getInstance​()


接口

为了能够定义方法,不书写方法体,同时,能够打破抽象类的单继承限制。Java 提供了接口实现多继承。

接口的结构由包含抽象方法和全局常量组成,接口中的内部结构可以增加内部类,使用关键字 interface 定义。

接口的使用原则如下:

1、接口中包含抽象方法和常量,不能使用关键字 new 进行实例化;

2、子类通过关键字 implements 实现多个接口;

3、接口必须使用子类进行实例化;

4、实现多个接口的子类,必须覆写所有的抽象方法;

5、接口通过子类对象向上转型实例化对象。

6、因为接口中只包含抽象方法和全局常量,故可以抽象方法和全局常量可以省略 public abstract 和 public static final。

7、一个抽象类可以继承一个抽象类,一个接口可以使用关键字 extends 继承多个接口,一个抽象类可以实现多个接口,一个接口不能继承抽象类;

*子接口对于父接口的继承,在 Java 的 IO 中便有所体现。OutputStream 字节输出流实现的接口 Closeable 便继承了一个新的父接口:public interface Closeable extends AutoCloseable

8、接口从概念上只由抽象方法和全局常量组成,但是实际上接口内部可以定义普通内部类、抽象内部类(abstract)、全局内部类(static)、内部接口,但是不能有 private 修改的私有内部类。

9、使用 static 修饰的接口内部接口,就相当于外部接口,在 Java 中的类集 Map.Entry 便应用了此方式。Map.Entry 定义:public static interface Map.Entry。

10、接口用于制定标准,实际应用于工厂设计模式、代理设计模式,这两种模式在框架上应用非常多,一定要细心掌握

11、代理设计模式的核心精髓就是有一个核心主题接口,可能包含多种行为,即拥有多个方法。代理主题类完成核心主题以外的所有行为,核心主题类完成核心主题任务



图 工厂设计模式



图 代理设计模式


接口的核心作用:

1、操作标准的定义;

2、一种标识,表示一种操作的能力,Java 提供 java.lang.Cloneable 克隆接口、java.io.Serializable 序列化接口便是这一体现,没有方法定义,只有接口名称;

3、将服务器端的远程方法试图暴露给客户端。

代码是否优秀的标准(接口思想,代理设计模式):

1、客户端调用简单,不需要关注细节;

2、客户端之外的代码进行修改,不影响客户端的使用;

由接口提炼出的工厂和代理设计模式,可以得出一个结论,现在程序的非常生活化,只要能够分析透彻,都可以使用程序实现,这也是创业很好的点,把生活业务研究透了,然后互联网创业

注意:

子类实现两个相互独立不同的接口,通过子类实例化和对象转型,将两个相互独立的接口联系到了一起,是因为他们有着共同的子类,由 new Z()决定,就是由谁开辟空间。


代码示例如下:


package org.fuys.ownutil.instance;interface X{void print();}interface Y{void print();}class Z implements X,Y{@Overridepublic void print() {System.out.println("Z");}}public class InterfaceInstance {public static void main(String[] args) {X x = new Z();Y y = (Y)x;y.print();System.out.println(x instanceof X);System.out.println(x instanceof Y);}}
复制代码


用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
Java面向对象_andy_InfoQ写作社区