1 类和对象 1.1 类的定义
是用来描述同一类事物的
可以在内部定义任意数量的、不同类型的变量,作为这一类事物的属性。这种属性叫做成员变量 ( member variable )。
如果一个Java文件中定义了一个public类,则文件名必须与该类的名称相同,包括大小写。
如果一个Java文件中定义了多个类,则只能有一个public类,该类的名称必须与文件名相同。
就好像文件路径+文件名不能重复一样,一个Java程序中相同名字的类只能有一个
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Merchandise { String name; String id; int count; double price; }
1.2 匿名对象 可以不定义对象的句柄,而直接调用这个对象的方法,如下:new Person().shout();
使用情况:
如果一个对象只需要执行一次方法调用,那么就可以使用匿名对象
将匿名对象作为实参传递给一个方法调用
1.3 引用类型 Java中的数据类型分为基本数据类型和引用数据类型
都可以用来创建变量,可以赋值和使用其值
本身都是一个地址
基本类型变量的值,就是地址对应的值。引用数据类型的值还是一个地址,需要通过“二级跳”找到实例
引用数据类型是Java的一种内部类型,是对所有自定义类型和数组引用的统称,并非特指某种类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Merchandise { String name; String id; int count; double price; } public class Merchandise { String name; String id; int count; double price; } public class ReferenceAndPrimaryDataType { public static void main (String[] args) { Merchandise m1; m1 = new Merchandise (); Merchandise m2 = new Merchandise (); Merchandise m3 = new Merchandise (); Merchandise m4 = new Merchandise (); Merchandise m5 = new Merchandise (); m1 = m5; System.out.println("m1=" + m1); System.out.println("m2=" + m2); System.out.println("m3=" + m3); System.out.println("m4=" + m4); System.out.println("m5=" + m5); Merchandise m6 = m1; System.out.println("m6=" + m6); m6 = m5; System.out.println("m6=" + m6); System.out.println("m1=" + m1); System.out.println("m2=" + m2); System.out.println("m3=" + m3); System.out.println("m4=" + m4); System.out.println("m5=" + m5); int a = 999 ; } }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 m1=Merchandise@1b6d3586 m2=Merchandise@4554617c m3=Merchandise@74a14482 m4=Merchandise@1540e19d m5=Merchandise@1b6d3586 m6=Merchandise@1b6d3586 m6=Merchandise@1b6d3586 m1=Merchandise@1b6d3586 m2=Merchandise@4554617c m3=Merchandise@74a14482 m4=Merchandise@1540e19d m5=Merchandise@1b6d3586
以上的m1 == m5 == m6,引用的地址一样。
1.4 类对象和引用的关系
类是对象的模版,对象是类的一个实例
一个Java程序中类名相同的类只能有一个,也就是类型不会重名
一个类可以有很多对象
一个对象只能根据一个类来创建
引用必须是、只能是一个类的引用
引用只能指向其所属的类型的类的对象
相同类型的引用之间可以赋值
只能通过指向一个对象的引用,来操作一个对象,比如访问某个成员变量
数组的类名就是类型带上中括号
同一类型的数组,每个数组对象的大小可以不一样,也就是每个数组对象占用的内存可以不一样 ,这点和类的对象不同。
可以用引用指向类型相同大小不同的数组,因为他们属于同一种类型
可以把类名当成自定义类型,定义引用的数组,甚至多维数组
示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ArrayIsClass { public static void main (String[] args) { int [] intArr; intArr = new int [1 ]; intArr = new int [2 ]; double [][][] double3DArray = new double [2 ][3 ][4 ]; int [] a1 = new int [9 ]; int [] a2 = new int [0 ]; a2 = a1; System.out.println("a2.length=" + a2.length); double [] a3 = new double [5 ]; double3DArray[1 ][2 ] = a3; } }
输出:
示例2
1 2 3 4 5 6 7 8 9 10 11 12 13 public class RefArray { public static void main (String[] args) { Merchandise[] merchandises = new Merchandise [9 ]; merchandises[0 ] = new Merchandise (); merchandises[1 ] = new Merchandise (); merchandises[0 ].name = "笔记本" ; System.out.println(merchandises[0 ].name); System.out.println(merchandises[2 ]); } }
输出:
1.5 对象的内存解析 1.5.1 JVM内存结构划分 HotSpot Java虚拟机的架构图如下。其中主要关心的是运行时数据区部分(Runtime Data Area)。
堆:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 栈:是指虚拟机栈。虚拟机栈用于存储局部变量等。 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
1.5.2 对象内存解析 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Person { int age; boolean isMale; } public class PersonTest { public static void main (String[] args) { Person p1 = new Person (); p1.name = "赵同学" ; p1.age = 20 ; p1.isMale = true ; Person p2 = new Person (); p2.age = 10 ; Person p3 = p1; p3.name = "郭同学" ; } }
内存解析图:
堆:凡是new出来的结构(对象、数组)都放在堆空间中。
对象的属性存放在堆空间中
创建一个类的多个对象(比如:p1、p2),则每个对象都拥有当前类的一套“副本”(即属性)。当通过一个对象修改其属性时,不会影响其他对象此属性的值
当声明一个新的变量使用现有的对象进行赋值时(比如p3=p1),两个变量会共同指向堆空间中同一个对象
1.6 类方法 1.6.1 构造方法
构造方法(constructor)的方法名必须与类名一样,而且构造方法没有返回值。这样的方法才是构造方法。
构造方法可以有参数,规则和语法于普通方法一样。使用时,参数传递给 new 语句后类名的括号后面。
如果没有显示的添加一个构造方法,Java会给每个类都会默认自带一个无参数的构造方法。
如果我们自己添加类构造方法,Java就不会再添加无参数的构造方法。这时候,就不能直接 new 一个对象不传递参数了(看例子)
所以我们一直都在使用构造方法,这也是为什么创建对象的时候类名后面要有一个括号的原因。
构造方法无法被点操作符调用或者在普通方法里调用,只能通过 new 语句在创建对象的时候,间接调用。
理解一下为什么构造方法不能有返回值,因为有返回值也没有意义,new 语句永远返回的是创建出来的对象的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.geekbang.supermarket;public class MerchandiseV2 { public String name; public String id; public int count = 999 ; public double soldPrice; public double purchasePrice; public MerchandiseV2 (String name, String id, int count, double soldPrice, double purchasePrice) { this .name = name; this .id = id; this .count = count; this .soldPrice = soldPrice; this .purchasePrice = purchasePrice; } public MerchandiseV2 (String name, String id, int count, double soldPrice) { this (name, id, count, soldPrice, soldPrice * 0.8 ); } public MerchandiseV2 () { this ("无名" , "000" , 0 , 1 , 1.1 ); } public void describe () { System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice + "。商品进价是" + purchasePrice + "。商品库存量是" + count + "。销售一个的毛利润是" + (soldPrice - purchasePrice)); } public double buy (int count) { if (this .count < count) { return -1 ; } return this .count -= count; } }
在构造方法里才能调用重载的构造方法。语法为this(实参列表)
构造方法不能自己调用自己,这会是一个死循环
在调用重载的构造方法时,不可以使用成员变量。因为用语意上讲,这个对象还没有被初始化完成,处于中间状态。
在构造方法里才能调用重载的构造方法时,必须是方法的第一行。后面可以继续有代码
1.6.2 静态变量和静态方法
静态变量
静态变量使用 static 修饰符
静态变量如果不赋值,Java也会给它赋以其类型的初始值
静态变量一般使用全大写字母加下划线分割。这是一个习惯用法
所有的代码都可以使用静态变量,只要根据防范控制符的规范,这个静态变量对其可见即可
public 的静态变量,所有的代码都可以使用它
如果没有public修饰符,只能当前包的代码能使用它
1 2 public static double DISCOUNT_FOR_VIP = 0.95 ;static int STATIC_VARIABLE_CURR_PACKAGE_ONLY = 100 ;
静态方法
静态方法中不能使用this自引用,其他和成员方法一样。
静态方法不属于某个实例,直接使用类名调用,所以静态方法中不能直接访问成员变量。
在静态方法里边,可以自己创建对象,或者通过参数,获取对象的引用调用方法和成员变量。
在当前类中访问静态方法可以省略类名
使用import static引入一个静态方法或静态变量。
静态方法的重载也是一样的,方法签名不同即可:方法名+参数类型
判断调用哪个方法,也是根据调用时参数匹配决定的。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.geekbang;import com.geekbang.supermarket.MerchandiseV2;import static com.geekbang.supermarket.MerchandiseV2.getVIPDiscount;public class MerchandiseV2DescAppMain { public static void main (String[] args) { MerchandiseV2 merchandise = new MerchandiseV2 ("书桌" , "DESK9527" , 40 , 999.9 , 500 ); merchandise.describe(); System.out.println(getVIPDiscount()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 package com.geekbang.supermarket;public class MerchandiseV2 { public String name; public String id; public int count; public double soldPrice; public double purchasePrice; public static double DISCOUNT_FOR_VIP = 0.95 ; public static double getVIPDiscount () { return DISCOUNT_FOR_VIP; } public static double getDiscountOnDiscount (LittleSuperMarket littleSuperMarket) { double activityDiscount = littleSuperMarket.activityDiscount; return DISCOUNT_FOR_VIP * activityDiscount; } public MerchandiseV2 (String name, String id, int count, double soldPrice, double purchasePrice) { this .name = name; this .id = id; this .count = count; this .soldPrice = soldPrice; this .purchasePrice = purchasePrice; } public String getName () { return name; } public MerchandiseV2 (String name, String id, int count, double soldPrice) { this (name, id, count, soldPrice, soldPrice * 0.8 ); } public MerchandiseV2 () { this ("无名" , "000" , 0 , 1 , 1.1 ); } public void describe () { System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice + "。商品进价是" + purchasePrice + "。商品库存量是" + count + "。销售一个的毛利润是" + (soldPrice - purchasePrice) + "。折扣为" + DISCOUNT_FOR_VIP); } public double calculateProfit () { double profit = soldPrice - purchasePrice; return profit; } public double buy () { return buy(1 ); } public double buy (int count) { return buy(count, false ); } public double buy (int count, boolean isVIP) { if (this .count < count) { return -1 ; } this .count -= count; double totalCost = count * soldPrice; if (isVIP) { return totalCost * getVIPDiscount(); } else { return totalCost; } } }
1.6.3 方法调用内存分析
方法没有被调用的时候,都在方法区中的字节码文件(.class)中存储。
方法被调用的时候,需要进入到栈内存中运行。方法每调用一次就会在栈中有一个入栈操作
当方法执行结束后,会释放该内存,称为出栈
1.6.4 方法的重载
方法签名 : 方法名+依次参数类型。注意,返回值不属于方法签名。方法签名是一个方法在一个类中的唯一标识
同一个类中方法可以重名,但是签名不可以重复。一个类中如果定义了名字相同,签名不同的方法,就叫做方法的重载
重载的参数匹配规则
如果有如下重载方法,在java中的自动类型转换匹配逻辑为:
1 2 public double buy (int count) {}public double buy (double count) {}
如果传的参数是 byte, short, int,类型会调用buy(int count)方法,如果是long, float, double 类型会调用buy(double count)方法。
无论是否重载参数类型可以不完全匹配的规则是”实参数可以自动类型转换成形参类型”
重载的特殊之处是,参数满足自动类型转换的方法有好几个,重载的规则是选择最”近”的去调用
1.6.5 可变个数的形参 格式:
1 2 public static void test (int a ,String...books) ;
特点:
方法的参数部分有可变形参,需要放在形参声明的最后
在一个方法的形参中,最多只能声明一个可变个数的形参
可变参数方法的使用与方法参数部分使用数组是一致的,二者不能同时声明,否则报错
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class StringTools { String concat (char seperator, String... args) { String str = "" ; for (int i = 0 ; i < args.length; i++) { if (i==0 ){ str += args[i]; }else { str += seperator + args[i]; } } return str; } }
1.6.6 参数传递机制:值传递 Java里方法的参数传递方式只有一种:值传递。
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参应用数据类型变量的“地址值”传递给形参
2 包和访问修饰符 2.1 package(包)
package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
不同的包里可以有相同名字的类
一个类只能有一个 package 语句,如果有 package 语句,则必须是类的第一行有效代码
包通常使用所在公司域名的倒置:com.atguigu.xxx
。
JDK中主要的包介绍:
java.lang
包含一些Java语言的核心类,如 String、Math、Integer、System和Thread,提供常用功能
java.net
包含执行与网络相关的操作的类和接口
java.io
包含能提供多种输入、输出功能的类
java.util
包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数
java.text
包含一些java格式化相关的类
java.sql
包含了java 进行JDBC数据库编程的相关类、接口
java.awt
包含了构成抽象窗口工具类的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)
2.2 import(导入)
使用import
每次使用都带包名很繁琐, 可以在使用的类的上面使用 import 语句, 一次性解决问题, 就可以直接使用类了。
1 2 import com.geekbang.supermarket.LittleSuperMarket;import com.geekbang.supermarket.MerchandiseV2;
如果使用 a.*
导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.*
的方式,一 次性导入util包下所有的类或接口。
如果导入的类或接口是java.lang
包下的,或者是当前包下的,则可以省略此import语句。
如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。
如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。
import static
组合的使用:调用指定类或接口下的静态的属性或方法
2.2 访问修饰符
属性访问修饰符:public
被 public 修饰的属性,可以被任意包中的类访问
没有访问修饰符的属性,称作缺省的访问修饰符,可以被本包内的其他类和自己的对象
访问修饰符是一种限制或者允许属性访问的修饰符
属性访问修饰符:protected
被protected修饰,可以在本类使用
可以在本包子类和非子类使用
在其他包中仅限子类可见
属性访问修饰符:priviate,仅可在本类可见
属性访问修饰符:缺省,可在本类和本包子类非子类可见,其他包不可见
实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控制。
修饰符
本类
本包
其他包子类
其他包非子类
private
√
×
×
×
缺省
√
√(本包子类非子类都可见)
×
×
protected
√
√(本包子类非子类都可见)
√(其他包仅限于子类中可见)
×
public
√
√
√
√
6. 类的全限定名
包名 + 类名 = 类的全限定名。也可以简称为类的全名
同一个 Java 程序中全限定名字不可重复
3 关键字:this 3.1 实例方法或构造器中使用当前对象的成员 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量。即:我们可以用this来区分成员变量
和局部变量
。比如:
使用this访问属性和方法时,如果在本类中未找到,会从父类中查找。
3.2 同一个类中构造器互相调用
this():调用本类的无参构造器
this(实参列表):调用本类的有参构造器
this()和this(实参列表)只能声明在构造器首行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Student { private String name; private int age; public Student () { } public Student (String name) { this (); this .name = name; } public Student (String name,int age) { this (name); this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } public String getInfo () { return "姓名:" + name +",年龄:" + age; } }
4 继承 4.1 继承的语法 通过 extends
关键字,可以声明一个类B继承另外一个类A,定义格式如下:
1 2 3 4 5 6 7 [修饰符] class 类A { ... } [修饰符] class 类B extends 类A { ... }
4.2 继承中的基本概念 类B,称为子类、派生类(derived class)、SubClass 类A,称为父类、超类、基类(base class)、SuperClass
4.3 继承性的细节
子类继承了父类的方法和属性
使用子类的引用可以调用父类的公有方法
使用子类的引用可以访问父类的公有属性
子类不能直接访问父类中私有的(private)的成员变量和方法,可通过继承的get/set方法进行访问。如图所示:
4.4 方法的重写(override/overwrite) 子类可以对从父类中继承来的方法进行改造,我们称为方法的重写 (override、overwrite)
。也称为方法的重置
、覆盖
。
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.inherited.method;public class Phone { public void sendMessage () { System.out.println("发短信" ); } public void call () { System.out.println("打电话" ); } public void showNum () { System.out.println("来电显示号码" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.inherited.method;public class SmartPhone extends Phone { @Override public void showNum () { System.out.println("显示来电姓名" ); System.out.println("显示头像" ); } @Override public void call () { System.out.println("语音通话 或 视频通话" ); } }
@Override使用说明: 写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
子类重写的方法必须
和父类被重写的方法具有相同的方法名称
、参数列表
。
子类重写的方法的返回值类型不能大于
父类被重写的方法的返回值类型。(例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
子类重写的方法使用的访问权限不能小于
父类被重写的方法的访问权限。(public > protected > 缺省 > private)
注意:① 父类私有方法不能重写 ② 跨包的父类缺省的方法也不能重写
子类方法抛出的异常不能大于父类被重写方法的异常 此外,子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
4.5 关键字:super 在Java类中使用super来调用父类中的指定操作:
super可用于访问父类中定义的属性
super可用于调用父类中定义的成员方法
super可用于在子类构造器中调用父类的构造器
注意:
尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
super的追溯不仅限于直接父类
super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
4.5.1 子类中调用父类被重写的方法
如果子类没有重写父类的方法,只要权限修饰符允许,在子类中完全可以直接调用父类的方法;
如果子类重写了父类的方法,在子类中需要通过super.
才能调用父类被重写的方法,否则默认调用的子类重写的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.atguigu.inherited.method;public class Phone { public void sendMessage () { System.out.println("发短信" ); } public void call () { System.out.println("打电话" ); } public void showNum () { System.out.println("来电显示号码" ); } } public class SmartPhone extends Phone { public void showNum () { System.out.println("显示来电姓名" ); System.out.println("显示头像" ); super .showNum(); } }
总结:
方法前面没有super.和this.
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
方法前面有this.
先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯
方法前面有super.
4.5.2 子类中调用父类中同名的成员变量
如果实例变量与局部变量重名,可以在实例变量前面加this.进行区别
如果子类实例变量和父类实例变量重名,并且父类的该实例变量在子类仍然可见,在子类中要访问父类声明的实例变量需要在父类实例变量前加super.,否则默认访问的是子类自己声明的实例变量
如果父子类实例变量没有重名,只要权限修饰符允许,在子类中完全可以直接访问父类中声明的实例变量,也可以用this.实例访问,也可以用super.实例变量访问
4.5.3 子类构造器中调用父类构造器 ① 子类继承父类时,不会继承父类的构造器。只能通过“super(形参列表)”的方式调用父类指定的构造器。 ② 规定:“super(形参列表)”,必须声明在构造器的首行。 ③ 我们前面讲过,在构造器的首行可以使用”this(形参列表)”,调用本类中重载的构造器, 结合②,结论:在构造器的首行,”this(形参列表)” 和 “super(形参列表)”只能二选一。 ④ 如果在子类构造器的首行既没有显示调用”this(形参列表)”,也没有显式调用”super(形参列表)”, 则子类此构造器默认调用”super()”,即调用父类中空参的构造器。 ⑤ 由③和④得到结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器。 只能是这两种情况之一。 ⑥ 由⑤得到:一个类中声明有n个构造器,最多有n-1个构造器中使用了”this(形参列表)”,则剩下的那个一定使用”super(形参列表)”。
开发中常见错误: 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参的构造器,则编译出错
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class A { A(int a){ System.out.println("A类有参构造器" ); } } class B extends A { B(){ System.out.println("B类无参构造器" ); } } class Test05 { public static void main (String[] args) { B b = new B (); } }
4. 6 继承里的静态方法: 静态方法可以被继承,静态方法不支持多态逻辑,建议在使用静态方法时直接使用类名调用静态方法,如果此类下没有这个方法,则会调用其父类下的静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 package com.geekbang.supermarket;public class Phone extends MerchandiseV2 { private double screenSize; private double cpuHZ; private int memoryG; private int storageG; private String brand; private String os; private static int MAX_BUY_ONE_ORDER = 5 ; public double buy (int count) { if (count > MAX_BUY_ONE_ORDER) { System.out.println("购买失败,手机一次最多只能买" + MAX_BUY_ONE_ORDER + "个" ); return -2 ; } return super .buy(count); } public String getName () { return this .brand + ":" + this .os + ":" + name; } public void describe () { System.out.println("此手机商品属性如下" ); super .describe(); System.out.println("手机厂商为" + brand + ";系统为" + os + ";硬件配置如下:\n" + "屏幕:" + screenSize + "寸\n" + "cpu主频" + cpuHZ + " GHz\n" + "内存" + memoryG + "Gb\n" + "存储空间" + storageG + "Gb" ); } public Phone getThisPhone () { return this ; } public void accessParentProps () { System.out.println("父类里的name属性:" + super .name); } public void useSuper () { super .describe(); super .buy(66 ); System.out.println("父类里的count属性:" + super .count); } public Phone ( String name, String id, int count, double soldPrice, double purchasePrice, double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os ) { this .screenSize = screenSize; this .cpuHZ = cpuHZ; this .memoryG = memoryG; this .storageG = storageG; this .brand = brand; this .os = os; this .setName(name); this .setId(id); this .setCount(count); this .setSoldPrice(soldPrice); this .setPurchasePrice(purchasePrice); } public boolean meetCondition () { return true ; } public double getScreenSize () { return screenSize; } public void setScreenSize (double screenSize) { this .screenSize = screenSize; } public double getCpuHZ () { return cpuHZ; } public void setCpuHZ (double cpuHZ) { this .cpuHZ = cpuHZ; } public int getMemoryG () { return memoryG; } public void setMemoryG (int memoryG) { this .memoryG = memoryG; } public int getStorageG () { return storageG; } public void setStorageG (int storageG) { this .storageG = storageG; } public String getBrand () { return brand; } public void setBrand (String brand) { this .brand = brand; } public String getOs () { return os; } public void setOs (String os) { this .os = os; } }
4.7 父类和子类的引用赋值关系
父类引用可以指向子类对象,子类引用不可以指向父类的对象
可以进行强制类型转换,如果类型不对,会报错
可以调用的方法,是受引用类型决定的
因为子类继承了父类的方法和属性,所以父类的对象能做到的,子类的对象肯定能做到
如果确定一个父类的引用指向的对象,实际上就是一个子类的对象(或者子类的子类的对象),可以强制类型转换,如果不是,则编译报错
如下示例: 类之间的关系为:LittleSuperMarket -> MerchandiseV2 -> Phone -> ShellColorChangePhone
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.geekbang;import com.geekbang.supermarket.MerchandiseV2;import com.geekbang.supermarket.Phone;import com.geekbang.supermarket.ShellColorChangePhone;public class ReferenceAssign { public static void main (String[] args) { Phone ph = new Phone ( "手机001" , "Phone001" , 100 , 1999 , 999 , 4.5 , 3.5 , 4 , 128 , "索尼" , "安卓" ); MerchandiseV2 m = ph; MerchandiseV2 m2 = new Phone ( "手机002" , "Phone002" , 100 , 1999 , 999 , 4.5 , 3.5 , 4 , 128 , "索尼" , "安卓" ); ph.getBrand(); Phone aPhone = (Phone) m2; ShellColorChangePhone shellColorChangePhone = new ShellColorChangePhone ( "手机002" , "Phone002" , 100 , 1999 , 999 , 4.5 , 3.5 , 4 , 128 , "索尼" , "安卓" ); MerchandiseV2 ccm = shellColorChangePhone; Phone ccp = (Phone) ccm; ShellColorChangePhone scp = (ShellColorChangePhone) ccm; ShellColorChangePhone notCCP = (ShellColorChangePhone) m2; } }
5 多态性 5.1 多态的形式和体现 对象的多态:在Java中,子类的对象可以替代父类的对象使用。所以,一个引用类型变量可能指向(引用)多种不同类型的对象
Java引用变量有两个类型:编译时类型
和运行时类型
。编译时类型由声明
该变量时使用的类型决定,运行时类型由实际赋给该变量的对象
决定。简称:编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
多态的使用前提:1. 类的继承关系 2. 方法的重写
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.polymorphism.grammar;public class Pet { private String nickname; public String getNickname () { return nickname; } public void setNickname (String nickname) { this .nickname = nickname; } public void eat () { System.out.println(nickname + "吃东西" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.polymorphism.grammar;public class Cat extends Pet { @Override public void eat () { System.out.println("猫咪" + getNickname() + "吃鱼仔" ); } public void catchMouse () { System.out.println("抓老鼠" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.polymorphism.grammar;public class Dog extends Pet { @Override public void eat () { System.out.println("狗子" + getNickname() + "啃骨头" ); } public void watchHouse () { System.out.println("看家" ); } }
1、方法内局部变量的赋值体现多态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.polymorphism.grammar;public class TestPet { public static void main (String[] args) { Pet pet = new Dog (); pet.setNickname("小白" ); pet.eat(); pet = new Cat (); pet.setNickname("雪球" ); pet.eat(); } }
2、方法的形参声明体现多态
1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.polymorphism.grammar;public class Person { private Pet pet; public void adopt (Pet pet) { this .pet = pet; } public void feed () { pet.eat(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.polymorphism.grammar;public class TestPerson { public static void main (String[] args) { Person person = new Person (); Dog dog = new Dog (); dog.setNickname("小白" ); person.adopt(dog); person.feed(); Cat cat = new Cat (); cat.setNickname("雪球" ); person.adopt(cat); person.feed(); } }
3、方法返回值类型体现多态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.polymorphism.grammar;public class PetShop { public Pet sale (String type) { switch (type){ case "Dog" : return new Dog (); case "Cat" : return new Cat (); } return null ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.atguigu.polymorphism.grammar;public class TestPetShop { public static void main (String[] args) { PetShop shop = new PetShop (); Pet dog = shop.sale("Dog" ); dog.setNickname("小白" ); dog.eat(); Pet cat = shop.sale("Cat" ); cat.setNickname("雪球" ); cat.eat(); } }
5.2 多态的好处和弊端 好处 :变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。弊端 :一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
1 2 3 4 5 6 Student m = new Student (); m.school = "pku" ; Person e = new Student (); e.school = "pku" ;
开发中:使用父类做方法的形参,是多态使用最多的场合。即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则。
5.3 虚方法调用 在Java中虚方法是指在编译阶段不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
1 2 Person e = new Student (); e.getInfo();
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。 前提:Person类中定义了welcome()方法,各个子类重写了welcome()。
拓展:静态链接(或早起绑定)
:当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。那么调用这样的方法,就称为非虚方法调用。比如调用静态方法、私有方法、final方法、父类构造器、本类重载构造器等。动态链接(或晚期绑定)
:如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。调用这样的方法,就称为虚方法调用。比如调用重写的方法(针对父类)、实现的方法(针对接口)。
5.5 instanceof操作符 instanceof 操作符,可以判断一个引用指向的对象是否是某一个类型或者其子类,是则返回true,否则返回false。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.geekbang;import com.geekbang.supermarket.LittleSuperMarket;import com.geekbang.supermarket.MerchandiseV2;import com.geekbang.supermarket.Phone;import com.geekbang.supermarket.ShellColorChangePhone;public class InstanceOfTestAppMain { public static void main (String[] args) { int merchandiseCount = 600 ; LittleSuperMarket superMarket = new LittleSuperMarket ("大卖场" , "世纪大道1号" , 500 , merchandiseCount, 100 ); for (int i = 0 ;i<merchandiseCount;i++){ MerchandiseV2 m = null ; if (m instanceof MerchandiseV2){ MerchandiseV2 ph = (MerchandiseV2)m; System.out.println(ph.getName()); }else { System.out.println("not an instance" ); } } } }
6 Object类的使用 6.1 根父类 所有的类,都直接或间接地继承自Object类。 Object类的对象可以指向任意类,Object类中没有属性,只有方法,定义的对象如下:
6.2 Object类中的方法: 6.2.1 equals 在Object类中定义判断两个对象的逻辑如下: 比较两个引用是否指向的是相同的类实例。
equals方法也是所有自定义类中覆盖实现比较多的方法,通过重新实现equals方法的逻辑,比较自定义类的对象。
程序示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.geekbang;import com.geekbang.supermarket.LittleSuperMarket;import java.util.Scanner;public class StringEqualsAppMain { public static void main (String[] args) { LittleSuperMarket superMarket = new LittleSuperMarket ("大卖场" , "世纪大道1号" , 500 , 600 , 100 ); String s1 = "aaabbb" ; String s2 = "aaa" + "bbb" ; System.out.println("s1和s2用==判断结果:" +(s1 == s2)); System.out.println("s1和s2用 equals 判断结果:" +(s1.equals(s2))); Scanner scanner = new Scanner (System.in); System.out.println("请输入s1" ); s1 = scanner.nextLine(); System.out.println("请输入s2" ); s2 = scanner.nextLine(); System.out.println("s1和s2用==判断结果:" +(s1 == s2)); System.out.println("s1和s2用 equals 判断结果:" +(s1.equals(s2))); } }
输出:
1 2 3 4 5 6 7 8 s1和s2用==判断结果:true s1和s2用 equals 判断结果:true 请输入s1 C:\Users\baihaoliang\.jdks\corretto-1.8.0_382\bin\java.exe -javaagent:D:\JetBrains\apps\IDEA-U\ch-0\231.9225.16\lib\idea_rt.jar=12228:D:\JetBrains\apps\IDEA-U\ch-0\231.9225.16\bin -Dfile.encoding=U 请输入s2 C:\Users\baihaoliang\.jdks\corretto-1.8.0_382\bin\java.exe -javaagent:D:\JetBrains\apps\IDEA-U\ch-0\231.9225.16\lib\idea_rt.jar=12228:D:\JetBrains\apps\IDEA-U\ch-0\231.9225.16\bin -Dfile.encoding=U s1和s2用==判断结果:false s1和s2用 equals 判断结果:true
String中定义的字符串是不可变的,所以按说在定义s1和s2时应该是不同的两个引用,但是在使用”==”进行比较时发现是相等的。这是因为在Java中定义字符串时,如果底层有一个相同的字符串,则不会重新定义,会把新的引用指向之前的字符串。 但是在定义的字符串很长时,则会突破这个优化,如上,输入了一个很长的字符串,使用"=="
比较是不相同的,但是使用equals是相同的,下边看下String类中对equals方法的覆盖实现: String中重新实现了equals方法按字符进行遍历比较,所以再长的字符串的情况下依然可以比较。
面试题: ==
和equals的区别
==
既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==
;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
具体要看自定义类里有没有重写Object的equals方法来判断。
通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
6.2.2 hashCode hashCode可以翻译为哈希码,或者散列码。应该是一个表示对象的特征的int整数。 自定义类中可以进行覆盖实现,来表示依次来表示类的唯一标识。
equals和hashCode是最常覆盖的两个方法,实现逻辑为,equals方法为true,则hashCode就相等,但hashCode相等,equals不一定为true。
6.2.3 toString 方法签名:public String toString() ① 默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式” ② 在进行String与其它类型数据的连接操作时,自动调用toString()方法
1 2 3 Date now=new Date (); System.out.println(“now=”+now); System.out.println(“now=”+now.toString());
③ 如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。
④ 可以根据需要在用户自定义类型中重写toString()方法 如String 类重写了toString()方法,返回字符串的值。
1 2 s1="hello" ; System.out.println(s1);
例如自定义的Person类:
1 2 3 4 5 6 7 8 9 public class Person { private String name; private int age; @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
6.2.4 finalize()
当对象被回收时,系统自动调用该对象的 finalize() 方法。(不是垃圾回收器调用的,是本类对象调用的)
永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
什么时候被回收:当某个对象没有任何引用时,JVM就认为这个对象是垃圾对象,就会在之后不确定的时间使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize()方法。
子类可以重写该方法,目的是在对象被清理之前执行必要的清理操作。比如,在方法内断开相关连接资源。
如果重写该方法,让一个新的引用变量重新引用该对象,则会重新激活对象。
在JDK 9中此方法已经被标记为过时
的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class FinalizeTest { public static void main (String[] args) { Person p = new Person ("Peter" , 12 ); System.out.println(p); p = null ; System.gc(); } } class Person { private String name; private int age; public Person (String name, int age) { super (); this .name = name; this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } @Override protected void finalize () throws Throwable { System.out.println("对象被释放--->" + this ); } @Override public String toString () { return "Person [name=" + name + ", age=" + age + "]" ; } }
6.2.5 getClass() public final Class<?> getClass()
:获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
1 2 3 4 public static void main (String[] args) { Object obj = new Person (); System.out.println(obj.getClass()); }
结果:
1 class com .atguigu.java.Person
7 关键字:static 7.1 static 关键字
使用范围:
在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点:
随着类的加载而加载
优先于对象存在
修饰的成员,被所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
7.2 静态变量
静态变量的默认值规则和实例变量一样。
静态变量值是所有对象共享。
静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
如果权限修饰符允许,在其他类中可以通过“类名.静态变量
”直接访问,也可以通过“对象.静态变量
”的方式访问(但是更推荐使用类名.静态变量的方式)。
静态变量的get/set方法也静态的,当局部变量与静态变量重名时
,使用“类名.静态变量
”进行区分。
7.3 静态方法
静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
静态方法可以被子类继承,但不能被子类重写。
静态方法的调用都只看编译时类型。
因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
示例:
1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.keyword;public class Father { public static void method () { System.out.println("Father.method" ); } public static void fun () { System.out.println("Father.fun" ); } }
1 2 3 4 5 6 7 8 package com.atguigu.keyword;public class Son extends Father { public static void fun () { System.out.println("Son.fun" ); } }
1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.keyword;public class TestStaticMethod { public static void main (String[] args) { Father.method(); Son.method(); Father f = new Son (); f.method(); } }
8 代码块 8.1 静态代码块
可以有输出语句。
可以对类的属性、类的声明进行初始化操作。
不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法 。
若有多个静态的代码块,那么按照从上到下的顺序依次执行。
静态代码块的执行要先于非静态代码块。
静态代码块随着类的加载而加载,且只执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.keyword;public class Chinese { private static String country; private String name; { System.out.println("非静态代码块,country = " + country); } static { country = "中国" ; System.out.println("静态代码块" ); } public Chinese (String name) { this .name = name; } }
8.2 非静态代码块 如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
可以有输出语句。
可以对类的属性、类的声明进行初始化操作。
除了调用非静态的结构外,还可以调用静态的变量或方法。
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
每次创建对象的时候,都会执行一次。且先于构造器执行。
9 final 修饰符 9.1 final修饰类:不可被继承 1 2 3 4 5 6 final class Eunuch { } class Son extends Eunuch { }
9.2 final修饰方法:不可被子类覆盖 1 2 3 4 5 6 7 8 9 10 class Father { public final void method () { System.out.println("father" ); } } class Son extends Father { public void method () { System.out.println("son" ); } }
9.3 final 修饰变量 final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量,常量名建议使用大写字母。 例如:final double MY_PI = 3.14;
构造方法不能用final修饰
使用final修饰方法的形参后,则无法在方法内修改形参变量
修饰静态变量:需要初始化,或者在static块中初始化,并且初始化后无法修改。
1 2 3 4 5 6 private final static int MAX_BUY_ONE_ORDER = 9 ;static { MAX_BUY_ONE_ORDER = 1 ; }
修饰属性:只能在定义时初始化或者构造方法中初始化,初始化后无法修改。
1 2 3 4 5 6 7 8 9 10 11 12 public final class Test { public static int totalNumber = 5 ; public final int ID; public Test () { ID = ++totalNumber; } public static void main (String[] args) { Test t = new Test (); System.out.println(t.ID); } }
修饰引用:引用类型本身初始化后无法修改,但可以通过引用修改引用的对象内容。
1 2 3 4 5 6 int [] array1 = new int [10 ];final int [] array = new int [10 ];array = array1; for (int b : array) { System.out.println(b); }
修饰局部变量:只能初始化一次,后边不能再次修改
1 2 3 4 5 6 7 8 public class TestFinal { public static void main (String[] args) { final int MIN_SCORE ; MIN_SCORE = 0 ; final int MAX_SCORE = 100 ; MAX_SCORE = 200 ; } }
10 抽象类与抽象方法 10.1 语法格式
抽象类 :被abstract修饰的类。
抽象方法 :被abstract修饰没有方法体的方法。
抽象类的语法格式
1 2 3 4 5 6 [权限修饰符] abstract class 类名{ } [权限修饰符] abstract class 类名 extends 父类{ }
抽象方法的语法格式
1 [其他修饰符] abstract 返回值类型 方法名([形参列表]);
注意:抽象方法没有方法体
代码举例:
1 2 3 public abstract class Animal { public abstract void eat () ; }
1 2 3 4 5 public class Cat extends Animal { public void eat () { System.out.println("小猫吃鱼和猫粮" ); } }
1 2 3 4 5 6 7 8 9 public class CatTest { public static void main (String[] args) { Cat c = new Cat (); c.eat(); } }
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法 。
10.2 使用说明
抽象类不能创建对象 ,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。 抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
抽象类的子类,必须重写抽象父类中所有的 抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
10.3 注意事项
不能用abstract修饰变量、代码块、构造器;
不能用abstract修饰私有方法、静态方法、final的方法、final的类。
11 接口(interface) 11.1 接口的声明格式 接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,枚举,接口,注解。
1 2 3 4 5 6 7 8 9 [修饰符] interface 接口名{ }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.atguigu.interfacetype;public interface USB3 { long MAX_SPEED = 500 *1024 *1024 ; void in () ; void out () ; default void start () { System.out.println("开始" ); } default void stop () { System.out.println("结束" ); } static void show () { System.out.println("USB 3.0可以同步全速地进行读写操作" ); } }
11.2 接口的使用规则 11.2.1 类实现接口 接口不能创建对象 ,但是可以被类实现(implements
,类似于被继承)。
类与接口的关系为实现关系,即类实现接口 ,该类可以称为接口的实现类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
1 2 3 4 5 6 7 8 9 【修饰符】 class 实现类 implements 接口{ } 【修饰符】 class 实现类 extends 父类 implements 接口{ }
如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法
。
默认方法可以选择保留,也可以重写。重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
接口中的静态方法不能被继承也不能被重写
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 interface USB { public void start () ; public void stop () ; } class Computer { public static void show (USB usb) { usb.start() ; System.out.println("=========== USB 设备工作 ========" ) ; usb.stop() ; } }; class Flash implements USB { public void start () { System.out.println("U盘开始工作。" ) ; } public void stop () { System.out.println("U盘停止工作。" ) ; } }; class Print implements USB { public void start () { System.out.println("打印机开始工作。" ) ; } public void stop () { System.out.println("打印机停止工作。" ) ; } }; public class InterfaceDemo { public static void main (String args[]) { Computer.show(new Flash ()) ; Computer.show(new Print ()) ; c.show(new USB (){ public void start () { System.out.println("移动硬盘开始运行" ); } public void stop () { System.out.println("移动硬盘停止运行" ); } }); } };
11.2.2 接口的多实现 在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现
。并且,一个类能继承一个父类,同时实现多个接口。实现格式:
1 2 3 4 5 6 7 8 9 【修饰符】 class 实现类 implements 接口1 ,接口2 ,接口3 。。。{ } 【修饰符】 class 实现类 extends 父类 implements 接口1 ,接口2 ,接口3 。。。{ }
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次 。
11.2.3 接口的继承 一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
Intf1接口:
1 2 3 4 5 package com.geekbang.intf; public interface Intf1 { void m1(); }
Intf2接口:
1 2 3 4 5 6 7 8 package com.geekbang.intf;public interface Intf2 { void m1 () ; void m2 () ; }
Intf3接口:
1 2 3 4 5 6 7 8 package com.geekbang.intf;public interface Intf3 extends Intf1 , Intf2{ void m3 () ; }
注意:
接口不可以继承类
接口不可以声明实例变量
如果一个类继承了两个接口,并且两个接口中的有相同的缺省方法,则编译报错。
接口中可以有静态方法,不需要用default修饰。静态方法可以被实现接口的类继承
接口中的this自引用,this引用的是实现这个接口的类所定义的实例
11.2.4 有方法的代码的接口
在Java 8中,接口允许有缺省实现的抽象方法。
缺省的实现方法,用default修饰,可以有方法体
接口中可以有私有方法,不需要用default修饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.geekbang.supermarket;import java.util.Date;public interface ExpireDateMerchandise { default boolean notExpireInDays (int days) { return daysBeforeExpire() > days; } private long daysBeforeExpire () { long expireMS = getExpireDate().getTime(); long left = expireMS - System.currentTimeMillis(); if (left < 0 ) { return -1 ; } return left / (24 * 3600 * 1000 ); } }
面对有缺省方法的接口,一个类继承时可以有三种选择:
默默继承,相当于类具有了这个方法的实现
覆盖,重新实现
把此方法声明为abstract,相当于把这个方法的实现拒之门外,但有abstrace方法的类,此类也就成了抽象类。
11.2.5 接口与实现类对象构成多态引用 实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口的不同实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.interfacetype;public class Mouse implements USB3 { @Override public void out () { System.out.println("发送脉冲信号" ); } @Override public void in () { System.out.println("不接收信号" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.interfacetype;public class KeyBoard implements USB3 { @Override public void in () { System.out.println("不接收信号" ); } @Override public void out () { System.out.println("发送按键信号" ); } }
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.atguigu.interfacetype;public class TestComputer { public static void main (String[] args) { Computer computer = new Computer (); USB3 usb = new Mouse (); computer.setUsb(usb); usb.start(); usb.out(); usb.in(); usb.stop(); System.out.println("--------------------------" ); usb = new KeyBoard (); computer.setUsb(usb); usb.start(); usb.out(); usb.in(); usb.stop(); System.out.println("--------------------------" ); usb = new MobileHDD (); computer.setUsb(usb); usb.start(); usb.out(); usb.in(); usb.stop(); } }
11.2.6 使用接口的静态成员 接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
1 2 3 4 5 6 7 8 9 10 package com.atguigu.interfacetype;public class TestUSB3 { public static void main (String[] args) { USB3.show(); System.out.println(USB3.MAX_SPEED); } }
11.2.7 使用接口的非静态方法
对于接口的静态方法,直接使用“接口名.
”进行调用即可
也只能使用“接口名.”进行调用,不能通过实现类的对象进行调用
对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.atguigu.interfacetype;public class TestMobileHDD { public static void main (String[] args) { MobileHDD b = new MobileHDD (); b.start(); b.in(); b.stop(); Usb3.show(); } }
11.3 接口的几种使用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class USBTest { public static void main (String[] args) { Computer computer = new Computer (); Printer printer = new Printer (); computer.transferData(printer); computer.transferData(new Camera ()); System.out.println(); USB usb1 = new USB (){ public void start () { System.out.println("U盘开始工作" ); } public void stop () { System.out.println("U盘结束工作" ); } }; computer.transferData(usb1); computer.transferData(new USB (){ public void start () { System.out.println("扫描仪开始工作" ); } public void stop () { System.out.println("扫描仪结束工作" ); } }); } }
11.4 接口与抽象类对比
12 内部类(InnerClass) 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass)
,类B则称为外部类(OuterClass)
。
根据内部类声明额未知,可以分为:
12.1 成员内部类 12.1.1 概述 如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。成员内部类的使用特征:
成员内部类作为类的成员的角色
:
和外部类不同,Inner class还可以声明为private或protected;
可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
成员内部类作为类的角色
:
可以在内部定义属性、方法、构造器等结构
可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
可以声明为abstract类 ,因此可以被其它的内部类继承
可以声明为final的,表示不能被继承
编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意:
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
成员内部类可以直接使用外部类的所有成员,包括私有的数据
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
12.1.2 创建成员内部类对象
实例化静态内部类
1 2 外部类名.静态内部类名 变量 = 外部类名.静态内部类名(); 变量.非静态方法();
实例化非静态内部类
1 2 3 外部类名 变量1 = new 外部类(); 外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名(); 变量2.非静态方法();
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class TestMemberInnerClass { public static void main (String[] args) { Outer.StaticInner inner = new Outer .StaticInner(); inner.inFun(); Outer.StaticInner.inMethod(); System.out.println("*****************************" ); Outer outer = new Outer (); Outer.NoStaticInner inner1 = outer.new NoStaticInner (); inner1.inFun(); Outer.NoStaticInner inner2 = outer.getNoStaticInner(); inner1.inFun(); } } class Outer { private static String a = "外部类的静态a" ; private static String b = "外部类的静态b" ; private String c = "外部类对象的非静态c" ; private String d = "外部类对象的非静态d" ; static class StaticInner { private static String a = "静态内部类的静态a" ; private String c = "静态内部类对象的非静态c" ; public static void inMethod () { System.out.println("Inner.a = " + a); System.out.println("Outer.a = " + Outer.a); System.out.println("b = " + b); } public void inFun () { System.out.println("Inner.inFun" ); System.out.println("Outer.a = " + Outer.a); System.out.println("Inner.a = " + a); System.out.println("b = " + b); System.out.println("c = " + c); } } class NoStaticInner { private String a = "非静态内部类对象的非静态a" ; private String c = "非静态内部类对象的非静态c" ; public void inFun () { System.out.println("NoStaticInner.inFun" ); System.out.println("Outer.a = " + Outer.a); System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("Outer.c = " + Outer.this .c); System.out.println("c = " + c); System.out.println("d = " + d); } } public NoStaticInner getNoStaticInner () { return new NoStaticInner (); } }
12.2 局部内部类 12.2.1 非匿名局部内部类 语法格式:
1 2 3 4 5 6 [修饰符] class 外部类{ [修饰符] 返回值类型 方法名(形参列表){ [final /abstract ] class 内部类{ } } }
编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
和成员内部类不同的是,它前面不能有权限修饰符等
局部内部类如同局部变量一样,有作用域
局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class TestLocalInner { public static void main (String[] args) { Outer.outMethod(); System.out.println("-------------------" ); Outer out = new Outer (); out.outTest(); System.out.println("-------------------" ); Runner runner = Outer.getRunner(); runner.run(); } } class Outer { public static void outMethod () { System.out.println("Outer.outMethod" ); final String c = "局部变量c" ; class Inner { public void inMethod () { System.out.println("Inner.inMethod" ); System.out.println(c); } } Inner in = new Inner (); in.inMethod(); } public void outTest () { class Inner { public void inMethod1 () { System.out.println("Inner.inMethod1" ); } } Inner in = new Inner (); in.inMethod1(); } public static Runner getRunner () { class LocalRunner implements Runner { @Override public void run () { System.out.println("LocalRunner.run" ); } } return new LocalRunner (); } } interface Runner { void run () ; }
12.2.2 匿名内部类 因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。
1 2 3 new 父类([实参列表]){ 重写方法... }
举例1:使用匿名内部类的对象直接调用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 interface A { void a () ; } public class Test { public static void main (String[] args) { new A (){ @Override public void a () { System.out.println("aaaa" ); } }.a(); } }
举例2:通过父类或父接口的变量多态引用匿名内部类的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface A { void a () ; } public class Test { public static void main (String[] args) { A obj = new A (){ @Override public void a () { System.out.println("aaaa" ); } }; obj.a(); } }
举例3:匿名内部类的对象作为实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 interface A { void method () ; } public class Test { public static void test (A a) { a.method(); } public static void main (String[] args) { test(new A (){ @Override public void method () { System.out.println("aaaa" ); } }); } }
13 枚举类(enum) 13.1 枚举的定义 1 2 3 4 5 6 7 8 9 【修饰符】 enum 枚举类名{ 常量对象列表 } 【修饰符】 enum 枚举类名{ 常量对象列表; 对象的实例变量列表; }
13.2 enum方式定义的要求和特点
枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
列出的实例系统会自动添加 public static final 修饰。
如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数
如果枚举类需要的是有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
switch提供支持枚举类型,case后面可以写枚举常量名,无需添加枚举类作为限定
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public enum SeasonEnum { SPRING("春天" ,"春风又绿江南岸" ), SUMMER("夏天" ,"映日荷花别样红" ), AUTUMN("秋天" ,"秋水共长天一色" ), WINTER("冬天" ,"窗含西岭千秋雪" ); private final String seasonName; private final String seasonDesc; private SeasonEnum (String seasonName, String seasonDesc) { this .seasonName = seasonName; this .seasonDesc = seasonDesc; } public String getSeasonName () { return seasonName; } public String getSeasonDesc () { return seasonDesc; } }
示例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu.enumeration;public enum Week { MONDAY("星期一" ), TUESDAY("星期二" ), WEDNESDAY("星期三" ), THURSDAY("星期四" ), FRIDAY("星期五" ), SATURDAY("星期六" ), SUNDAY("星期日" ); private final String description; private Week (String description) { this .description = description; } @Override public String toString () { return super .toString() +":" + description; } }
13.3 enum中常用方法 1 2 3 4 5 String toString () : 默认返回的是常量名(对象名),可以继续手动重写该方法! static 枚举类型[] values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值,是一个静态方法static 枚举类型 valueOf(String name):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。String name () :得到当前枚举常量的名称。建议优先使用toString()。 int ordinal () :返回当前枚举常量的次序号,默认从0 开始
13.4 实现接口的枚举类
和普通 Java 类一样,枚举类可以实现一个或多个接口
若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enum A implements 接口1 ,接口2 { } enum A implements 接口1 ,接口2 { 常量名1 (参数){ }, 常量名2 (参数){ }, }
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 interface Info { void show () ; } enum Season1 implements Info { SPRING("春天" ,"春暖花开" ){ public void show () { System.out.println("春天在哪里?" ); } }, SUMMER("夏天" ,"夏日炎炎" ){ public void show () { System.out.println("宁静的夏天" ); } }, AUTUMN("秋天" ,"秋高气爽" ){ public void show () { System.out.println("秋天是用来分手的季节" ); } }, WINTER("冬天" ,"白雪皑皑" ){ public void show () { System.out.println("2002年的第一场雪" ); } }; private final String SEASON_NAME; private final String SEASON_DESC; private Season1 (String seasonName,String seasonDesc) { this .SEASON_NAME = seasonName; this .SEASON_DESC = seasonDesc; } public String getSEASON_NAME () { return SEASON_NAME; } public String getSEASON_DESC () { return SEASON_DESC; } }
14 包装类 14.1 有那些包装类 Java针对八种基本数据类型定义了相应的引用类型:包装类(封装类)。有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
封装以后的,内存结构对比:
1 2 3 4 public static void main (String[] args) { int num = 520 ; Integer obj = new Integer (520 ); }
14.2 包装类与基本数据类型间的转换 14.2.1 自动装箱与拆箱 由于我们经常要做基本类型与包装类之间的转换,从JDK5.0
开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
1 2 3 Integer i = 4 ;i = i + 5 ;
注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。
1 2 Integer i = 1 ;Double d = 1 ;
14.2.2 基本数据类型、包装类与字符串间的转换 (1)基本数据类型转为字符串
方式1: 调用字符串重载的valueOf()方法
1 2 3 4 int a = 10 ;String str = String.valueOf(a);
方式2: 更直接的方式
1 2 3 int a = 10 ;String str = a + "" ;
(2)字符串转为基本数据类型
方式1: 除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:
public static int parseInt(String s)
:将字符串参数转换为对应的int基本类型。
public static long parseLong(String s)
:将字符串参数转换为对应的long基本类型。
public static double parseDouble(String s)
:将字符串参数转换为对应的double基本类型。
方式2: 字符串转为包装类,然后可以自动拆箱为基本数据类型
public static Integer valueOf(String s)
:将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型
public static Long valueOf(String s)
:将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型
public static Double valueOf(String s)
:将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型
注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException
异常。
方式3: 通过包装类的构造器实现
1 2 3 4 5 6 7 8 9 int a = Integer.parseInt("整数的字符串" );double d = Double.parseDouble("小数的字符串" );boolean b = Boolean.parseBoolean("true或false" );int a = Integer.valueOf("整数的字符串" );double d = Double.valueOf("小数的字符串" );boolean b = Boolean.valueOf("true或false" );int i = new Integer (“12 ”);
其他方式小结:
14.3 包装类的API 14.3.1 数据类型的最大最小值 1 2 3 Integer.MAX_VALUE和Integer.MIN_VALUE Long.MAX_VALUE和Long.MIN_VALUE Double.MAX_VALUE和Double.MIN_VALUE
14.3.2 字符转大小写 1 2 Character.toUpperCase('x' ); Character.toLowerCase('X' );
14.3.3 整数转进制 1 2 3 Integer.toBinaryString(int i) Integer.toHexString(int i) Integer.toOctalString(int i)
14.3.4 比较的方法 1 2 Double.compare(double d1, double d2) Integer.compare(int x, int y)
14.4 包装类对象的特点 14.4.1 包装类缓存对象
包装类
缓存对象
Byte
-128~127
Short
-128~127
Integer
-128~127
Long
-128~127
Float
没有
Double
没有
Character
0~127
Boolean
true和false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Integer a = 1 ;Integer b = 1 ;System.out.println(a == b); Integer i = 128 ;Integer j = 128 ;System.out.println(i == j); Integer m = new Integer (1 ); Integer n = 1 ; System.out.println(m == n); Integer x = new Integer (1 ); Integer y = new Integer (1 ); System.out.println(x == y);
1 2 3 Double d1 = 1.0 ;Double d2 = 1.0 ;System.out.println(d1==d2);
14.4.2 类型转换问题 1 2 3 Integer i = 1000 ;double j = 1000 ;System.out.println(i==j);
1 2 3 Integer i = 1000 ;int j = 1000 ;System.out.println(i==j);
1 2 3 Integer i = 1 ;Double d = 1.0 System.out.println(i==d);
14.4.3 包装类对象不可变 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TestExam { public static void main (String[] args) { int i = 1 ; Integer j = new Integer (2 ); Circle c = new Circle (); change(i, j, c); System.out.println("i = " + i); System.out.println("j = " + j); System.out.println("c.radius = " + c.radius); } public static void change (int a, Integer b, Circle c) { a += 10 ; c.radius += 10 ; } } class Circle { double radius; }
15 单例(Singleton)设计模式 15.1 什么是单例模式 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。
实现思路: 首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
15.2 单例模式的两种实现方式 15.2.1 饿汉式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Singleton { private Singleton () { } private static Singleton single = new Singleton (); public static Singleton getInstance () { return single; } }
15.2.2 懒汉式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Singleton { private Singleton () { } private static Singleton single; public static Singleton getInstance () { if (single == null ) { single = new Singleton (); } return single; } }
存在多线程安全问题
使用同步机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.atguigu.single.lazy;public class LazyOne { private static LazyOne instance; private LazyOne () {} public static synchronized LazyOne getInstance1 () { if (instance == null ){ instance = new LazyOne (); } return instance; } public static LazyOne getInstance2 () { synchronized (LazyOne.class) { if (instance == null ) { instance = new LazyOne (); } return instance; } } public static LazyOne getInstance3 () { if (instance == null ){ synchronized (LazyOne.class) { try { Thread.sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } if (instance == null ){ instance = new LazyOne (); } } } return instance; } }
使用内部类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.single.lazy;public class LazySingle { private LazySingle () {} public static LazySingle getInstance () { return Inner.INSTANCE; } private static class Inner { static final LazySingle INSTANCE = new LazySingle (); } }
内部类只有在外部类被调用才加载,产生INSTANCE实例;又不用加锁。 此模式具有之前两个模式的优点,同时屏蔽了它们的缺点,是最好的单例模式。 此时的内部类,使用enum进行定义,也是可以的。
15.2.3 饿汉式 vs 懒汉式 饿汉式:
特点:立即加载
,即在使用类的时候已经将对象创建完毕。
优点:实现起来简单
;没有多线程安全问题。
缺点:当类被加载的时候,会初始化static的实例,静态变量被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会耗费内存
。
懒汉式:
特点:延迟加载
,即在调用静态方法时实例才被创建。
优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会节约内存
。
缺点:在多线程环境中,这种实现方法是完全错误的,线程不安全
,根本不能保证单例的唯一性。
说明:在多线程章节,会将懒汉式改造成线程安全的模式。