面向对象
在计算机语言开发的历史长河中,先后出现了面向过程和面向对象的编程思想。由于面向过程过于复杂,代码不可重复使用,故 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 array
char []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 array
Mouse[] mouses = new Mouse[]{new Mouse("logitech")};
for(int x = 0;x<mouses.length;x++){
System.out.println(mouses[x].getBrand());
}
// 2 dimensional array
int[][] 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 method
int [][] 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); // false
String c = "yun";
System.out.println(a==c); // true
System.out.println(b==c); // false
b.intern();
String d = "yun";
System.out.println(a==d); // true
System.out.println(b==d); // false
System.out.println(c==d); // true
String e = new String(a).intern();
String f = "yun";
System.out.println(e==b); // false
System.out.println(e==a); // true
System.out.println(e==f); // true
System.out.println(a==c&c==d&d==e&e==f); // true
System.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); // false
System.out.println(b==d); // false
System.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,null
if(computer == null)
return 1;
// 2,address
if(this == computer)
return 0;
// 3,fields
if(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,null
if(obj == null)
return 1;
// 2,address
if(this == obj)
return 0;
// 3,instanceof
if((obj instanceof Computer)==false){
return -1;
}
Computer computer = (Computer) obj;
// 4,fields
if(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 used
static{
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 Block
synchronized(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 block
static{
outerStaticField = "Hello World!!!";
}
// private field
private String outerField;
// static private field
private static String outerStaticField;
// constructor method
public Outer(String outerField){
this.outerField = outerField;
}
// normal inner class
class Inner1{
public void getOuterField(){
// access outer class field
System.out.println(Outer.this.outerField);
}
}
// private inner class
private 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 class
static 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 {
@Override
public 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{
@Override
public 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;
}
@Override
public 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{
@Override
public 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);
}
}
复制代码
评论