文章目录
学习面向对象内容的三条主线
- Java类及类的成员(属性、方法、构造器;代码块、内部类)
- 面向对象的三大特性(封装性、继承性、多态性、(抽象性))
- 其他关键字(this、super、static、final、abstract、interface、package、import等)
面向过程(POP)与面向对象(OOP)
- 二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
- 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
- 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
- 面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
面向对象的思想概述
-
程序员从面向过程的执行者转化成了面向对象的指挥者
-
面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
-
类(Class)和对象(Object)是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)
- 面向对象程序设计的重点是类的设计
- 类的设计,其实就是类的成员的设计
Java类和对象
Java中用类class来描述事物也是如此。常见的类的成员有:
- 属性:对应类中的成员变量
- 行为:对应类中的成员方法
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
创建类的对象 = 类的实例化 = 实例化类
创建Java自定义类
步骤如下:
- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
对象的创建和使用
-
创建对象语法: 类名 对象名 = new 类名();
-
使用“对象名.对象成员”的方式访问对象成员(包括属性和方法)
-
如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰
-
类的访问机制:
-
在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过)
-
在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
-
对象的创建和使用:匿名对象
- 我们创建的对象,没显式的赋给一个变量名。即为匿名对象
- 特点是:匿名对象只能调用一次
- 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。如:new Person().shout();
- 使用情况
- 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象
- 我们经常将匿名对象作为实参传递给一个方法调用
类的成员之一:属性(Field)
变量的分类:成员变量与局部变量
- 在方法体外,类体内声明的变量称为成员变量;
- 在方法体内声明的变量称为局部变量
成员变量(属性)与局部变量的异同
-
相同点
- 定义变量的格式:数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都其对应的作用域
-
不同点
- 在类中声明的位置的不同
- 成员变量:直接定义在类的一对{}内
- 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
- 关于权限修饰符的不同
- 成员变量:可以在声明属性时,指明其权限,使用权限修饰符。常用的权限修饰符:private、public、缺省、protected。目前,大家声明属性时,都使用缺省就可以了。
- 局部变量:不可以使用权限修饰符。
- 默认初始化值的情况:
- 属性:类的属性,根据其类型,都默认初始化值。
- 整型(byte、short、int、long:0)
- 浮点型(float、double:0.0)
- 字符型(char:0 (或’\u0000’))
- 布尔型(boolean:false)
- 引用数据类型(类、数组、接口:null)
- 局部变量:没默认初始化值。意味着,我们在调用局部变量之前,一定要显式赋值(特别地:形参在调用时,我们赋值即可)
- 属性:类的属性,根据其类型,都默认初始化值。
- 在内存中加载的位置:
- 属性:加载到堆空间中 (非static)
- 局部变量:加载到栈空间
- 在类中声明的位置的不同
成员变量(属性)和局部变量的主要区别?
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final 修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间或者静态域内 | 栈空间 |
类的成员之二:方法
(1)什么是方法(method、函数)
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里
方法的分类:按照是否有形参及返回值
无返回值 | 有返回值 | |
---|---|---|
无形参 | void 方法名(){} | 返回值的类型 方法名(){} |
有形参 | void 方法名(形参列表){} | 返回值的类型 方法名(形参列表){} |
(2)方法的调用
-
方法通过方法名被调用,且只有被调用才会执行
-
方法调用的过程分析
特别说明
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理
- 方法中只能调用方法或属性,不可以在方法内部定义方法
(3)方法的重载
概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
特点
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
重载示例
//如下的4个方法构成了重载
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double d1,double d2){
System.out.println("2");
}
public void getSum(String s ,int i){
System.out.println("3");
}
public void getSum(int i,String s){
System.out.println("4");
}
不构成重载的举例:
//如下的3个方法不能与上述4个方法构成重载
public int getSum(int i,int j){
return 0;
}
public void getSum(int m,int n){
}
private void getSum(int i,int j){
}
总结
“两同一不同”
- 同一个类、相同方法名
- 参数列表不同:参数个数不同,参数类型不同
如何判断是否构成方法的重载?
- 严格按照定义判断:两同一不同
- 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系
(4)可变个数形参的方法
JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
使用说明
- 声明格式:
方法名(参数的类型名 ...参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在一个方法的形参位置,最多只能声明一个可变个数形参
可变个数形参示例
public void show(int i){
}
public void show(String s){
System.out.println("show(String)");
}
public void show(String ... strs){
System.out.println("show(String ... strs)");
for(int i = 0;i < strs.length;i++){
System.out.println(strs[i]);
}
}
//不能与上一个方法同时存在
// public void show(String[] strs){
//
// }
public void test(String[] msg){
System.out.println("含字符串数组参数的test方法 ");
}
public void test1(String book){
System.out.println("****与可变形参方法构成重载的test1方法****");
}
public void test1(String ... books){
System.out.println("****形参长度可变的test1方法****");
}
public static void main(String[] args){
TestOverload to = new TestOverload();
//下面两次调用将执行第二个test方法
to.test1();
to.test1("aa" , "bb");
//下面将执行第一个test方法
to.test(new String[]{"aa"});
}
(5)Java的值传递机制
- 方法,必须由其所在类或对象调用才有意义。若方法含有参数:
- 形参:方法声明时的参数
- 实参:方法调用时实际传给形参的参数值
- Java的实参值如何传入方法呢?
- Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的==“数据值”==传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的==“地址值”==传递给形参
(1)针对于方法内变量的赋值示例
@Test
public void test(){
System.out.println("***********基本数据类型:****************");
int m = 10;
int n = m;
System.out.println("m = " + m + ", n = " + n);
n = 20;
System.out.println("m = " + m + ", n = " + n);
System.out.println("***********引用数据类型:****************");
Order o1 = new Order();
o1.orderId = 1001;
Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向了堆空间中同一个对象实体。
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);
o2.orderId = 1002;
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);
}
规则:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
(2)针对于方法的参数
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传递给形参的数据
(3)Java中参数传递机制:值传递
规则:
- 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
- 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
推广:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
(4)示例及内存解析
(6)递归方法
定义
递归方法:一个方法体内调用它自身
如何理解递归方法
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环
示例
// 例1:计算1-n之间所自然数的和
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
// 例2:计算1-n之间所自然数的乘积:n!
public int getSum1(int n) {
if (n == 1) {
return 1;
} else {
return n * getSum1(n - 1);
}
}
//例3:已知一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大于0的整数,求f(10)的值。
public int f(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
return 2*f(n - 1) + f(n - 2);
}
}
类的成员之三:构造器
(1)构造器或者构造方法的作用
- 创建对象
- 初始化对象的信息
(2)构造器的特征
- 它具有与类相同的名称
- 它不声明返回值类型(与声明为void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
(3)构造器使用说明
- 如果没显式的定义类的构造器的话,则系统默认提供一个空参的构造器
- 定义构造器的格式:
权限修饰符 类名(形参列表){}
- 一个类中定义的多个构造器,彼此构成重载
- 一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
- 一个类中,至少会有一个构造器
(4)示例
public class Person{
//构造器
public Person(){
System.out.println("Person().....");
}
public Person(String n){
name = n;
}
public Person(String n,int a){
name = n;
age = a;
}
}
(5)构造器重载
-
构造器一般用来创建对象的同时初始化对象
-
构造器重载使得对象的创建更加灵活,方便创建各种不同的对象,构造器重载示例如下:
public class Person{ public Person(String name, int age, Date d) {this(name,age);…} public Person(String name, int age) {…} public Person(String name, Date d) {…} public Person(){…} }
-
构造器重载,参数列表必须不同
(6)属性赋值过程
可以在很多位置对类的属性赋值,总结如下
- 赋值的位置:
- ① 默认初始化
- ② 显式初始化
- ③ 构造器中初始化
- ④ 通过“对象.属性“或“对象.方法”的方式赋值
- 赋值的先后顺序为:①-②-③-④
(7)JavaBean的概念
- JavaBean是一种Java语言写成的可重用组件。
- 所谓javaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
- 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象
- 用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变
类的成员之四:代码块
(1)代码块的作用
- 用来初始化类、对象的信息
(2)分类
-
一个类中代码块若有修饰符,则只能被
static
修饰,称为静态代码块(static block),没有使用static
修饰的,称为非静态代码块 -
static代码块通常用于初始化static的属性
class Person { public static int total; static { total = 100;//为total赋初值 } …… //其它属性或方法声明 }
(3)静态代码块VS非静态代码块
- 静态代码块:(用static修饰的代码块)
- 可以有输出语句
- 可以对类的属性、类的声明进行初始化操作
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行
- 静态代码块的执行要先于非静态代码块
- 静态代码块随着类的加载而加载,且只执行一次
- 作用:初始化类的信息
- 非静态代码块(没有用static修饰的代码块)
- 可以有输出语句
- 可以对类的属性、类的声明进行初始化操作
- 除了调用非静态的结构外,还可以调用静态的变量或方法
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
- 每次创建对象的时候,都会执行一次。且先于构造器执行
(4)静态初始化块示例
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
public class PersonTest {
public static void main(String[] args) {
System.out.println("total = " + Person.total);
System.out.println("total = " + Person.total);
}
}
输出结果为:
in static block!
total = 100
total = 100
(5)实例化子类对象时,涉及到父类、子类中静态代码块、非静态代码块、构造器的加载顺序
加载顺序为:由父及子(先父类后子类),静态先行
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}
输出结果为:
11111111111
44444444444
77777777777
————————————————————————————————
22222222222
33333333333
55555555555
66666666666
————————————————————————————————
22222222222
33333333333
55555555555
66666666666
————————————————————————————————
22222222222
33333333333
class Root {
static {
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root() {
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root {
static {
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid() {
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg) {
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid {
static {
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf() {
//通过super调用父类中有一个字符串参数的构造器
super("htzw");
System.out.println("Leaf的构造器");
}
}
public class LeafTest {
public static void main(String[] args) {
new Leaf();
System.out.println("——————————————————");
new Leaf();
}
}
输出结果为:
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:htzw
Leaf的普通初始化块
Leaf的构造器
——————————————————
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:htzw
Leaf的普通初始化块
Leaf的构造器
类的成员之五:内部类
(1)定义
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称
(2)内部类的分类
成员内部类(静态、非静态 ) vs 局部内部类(方法内、代码块内、构造器内)
(3)成员内部类的理解
- 作为外部类的成员
- 调用外部类的结构
- 可以被
static
修饰 - 可以被4种不同的权限修饰
- 作为一个类
- 类内可以定义属性、方法、构造器等
- 可以被
final
修饰,表示此类不能被继承 - 可以被
abstract
修饰
(4)如何创建成员内部类的对象?
-
创建静态的Dog内部类的实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
-
创建费静态的Bird内部类的实例(非静态的成员内部类)
//Person.Bird bird = new Person.Bird();//错误的 Person p = new Person(); Person.Bird bird = p.new Bird();
(5)如何在成员内部类中调用外部类的结构?
class Person {
String name = "小明";
public void eat() {
}
//非静态成员内部类
class Bird {
String name = "杜鹃";
public void display(String name) {
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
//Person.this.eat();
}
}
}
面向对象特征之一:封装性
(1)为什么要引入封装性?
- 程序设计追求==“高内聚,低耦合”==。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅对外暴露少量的方法用于使用。
- 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
(2)问题引入
当我们创建一个类的对象以后,我们可以通过"对象.属性"
的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private)
(3)信息的封装和隐藏
Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改,增强代码的可维护性
(4)封装性思想具体的代码体现
- 体现一:将类的属性xxx私化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
private double radius;
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return radius;
}
- 体现二:不对外暴露的私有的方法
- 体现三:单例模式(将构造器私有化)
- 体现四:如果不希望类在包外被调用,可以将类设置为缺省的
(5)Java规定的四种权限修饰符
- 权限从小到大顺序为:private < 缺省<protected<public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Y | |||
缺省 | Y | Y | ||
protected | Y | Y | Y | |
public | Y | Y | Y | Y |
- 4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
- 修饰类的话,只能使用:缺省、public
- public类可以在任意地方被访问
- default类只可以被同一包内部的类访问
面向对象特征之二:继承性
(1)为什么要有类的继承性?(继承性的好处)
- 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可
- 减少了代码的冗余,提高了代码的复用性
- 继承的出现,便于功能的扩展
- 继承的出现让类与类之间产生了关系,为之后多态性的使用,提供了前提
- 此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。可以理解为:“子类 is a 父类”
(2)继承性的格式
class A extends B {}
- A:子类、派生类
- B:父类、超类、基类
(3)子类继承父类以后有哪些不同?
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法
特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。 - 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- 子类和父类的关系,不同于子集和集合的关系
- extends:延展、扩展
(4)Java中继承性的说明
- 一个类可以被多个子类继承
- Java中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了直接父类以及所间接父类中声明的属性和方法
继承成员变量和继承方法的区别示例
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);//20
s.display();//20
Base b = s;
System.out.println(b == s);//true
System.out.println(b.count);//10
b.display();//20
}
}
说明如下:
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
面向对象特征之三:多态性
(1)多态性的理解
-
可以理解为一个事物的多种形态
-
对象的多态性:父类的引用指向子类的对象(或者子类的对象赋给父类的引用)
可以直接应用在抽象类和接口上
-
Java引用变量有两个类型:编译时类型和运行时类型
- 编译时类型由声明该变量时使用的类型决定
- 运行时类型由实际赋给该变量的对象决定
- 简称:编译时,看左边;运行时,看右边
-
- 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
- “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
-
对象的多态——在Java中,子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象
-
子类可以看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)
(2)多态性的使用:虚拟方法调用
-
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的
-
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法
Person e = new Student(); e.getInfo(); //调用Student类的getInfo()方法
-
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类 的getInfo()方法。——动态绑定
(3)多态性的使用前提条件
①类的继承关系;②方法的重写
(4)多态性使用说明
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
(5)总结
-
多态的作用
- 提高了代码的通用性,称作接口重用
-
使用前提
- 需要存在继承或者实现关系
- 有方法的重写
-
成员方法
- 编译时:要查看引用变量所声明的类中是否有所调用的方法
- 运行时:调用实际new的对象所属的类中的重写方法
-
成员变量
- 不具备多态性,只看引用变量所声明的类
面试题:多态是编译时行为还是运行时行为?如何证明
- 多态是运行时行为
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat();
case 1:
return new Dog();
default:
return new Sheep();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
面试题,以下程序的输出结果
public class InterviewTest {
public static void main(String[] args) {
Base base = new Sub();
base.add(1, 2, 3);
Sub s = (Sub)base;
s.add(1,2,3);
}
}
class Base {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub extends Base {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
输出结果为:
sub_1
sub_2
Object类的使用
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类 为
java.lang.Object
类 -
所的java类(除java.lang.Object类之外都直接或间接的继承于
java.lang.Object
类 -
意味着,所的java类具有
java.lang.Object
类声明的功能
public class Person {
...
}
等价于:
public class Person extends Object {
...
}
- Object类中包含的方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()/wait() / notify()/notifyAll()
(1)equals()方法
-
是一个方法,而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的定义:
public boolean equals(Object obj) { return (this == obj); }
-
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体
-
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同
-
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的==“实体内容”==是否相同。那么,我们就需要对Object类中的equals()进行重写
-
重写的原则:比较两个对象的实体内容是否相同
自定义类如何重写equals()
class User {
String name;
int age;
//重写其equals()方法
public boolean equals(Object obj) {
//首先判断两个对象的地址是否相同,假如相同,后续就无需在进行比较
if (obj == this) {
return true;
}
if (obj instanceof User) {
User u = (User) obj;
return this.age == u.age && this.name.equals(u.name);
}
return false;
}
}
重写equals()方法的规则
- 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”
- 自反性:x.equals(x)必须返回是“true”
- 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”
- 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”
- 任何情况下,x.equals(null),永远返回是“false”
- x.equals(和x不同类型的对象)永远返回是“false”
相关示例
public class Test {
public static void main(String[] args) {
int it = 65;
float fl = 65.0f;
System.out.println("65和65.0f是否相等?" + (it == fl)); //true
char ch1 = 'A';
char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println("12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?" + (str1 == str2));//false
System.out.println("str1是否equals str2?" + (str1.equals(str2)));//true
// System.out.println("hello" == new java.util.Date()); //编译不通过
}
}
(2)==操作符
-
基本类型比较值:只要两个变量的值相等,即为true
-
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true
-
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
-
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
-
== 符号使用时,必须保证符号左右两边的变量类型一致
(3)toString()方法
-
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址
-
在进行String与其它类型数据的连接操作时,自动调用toString()方法
-
可以根据需要在用户自定义类型中重写toString()方法
-
基本类型数据转换为String类型时,调用了对应包装类的toString()方法
-
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
-
Object类中toString()的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息
-
自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
以下程序输出结果
@org.junit.Test
public void test() {
char[] arr = new char[] { 'a', 'b', 'c' };
System.out.println(arr);//abc
int[] arr1 = new int[] { 1, 2, 3 };
System.out.println(arr1);//[I@5479e3f
double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
System.out.println(arr2);//[D@27082746
}
面试题:==和equals()的区别是什么
- ==既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals的话,它是属于
java.lang.Object
类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点 - 具体要看自定义类里有没有重写Object的equals方法来判断
- 通常情况下,重写equals方法,会比较类中的相应属性是否都相等
包装类(Wrapper)的使用
(1)为什么要有包装类(或封装类)
- 为了使基本数据类型的变量具有类的特征,引入包装类。
- 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
(2)基本数据类型与对应的包装类
- 装箱:基本数据类型包装成包装类的实例,包装类使得一个基本数据类型的数据变成了类,有了类的特点,可以调用类中的方法
- 拆箱:获得包装类对象中包装的基本类型变量
- JDK5以后,支持自动装箱、自动拆箱,但类型必须匹配
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
(3)基本数据类型、包装类与String之间的转换流程
- 基本数据类型<—>包装类:JDK 5.0 新特性:自动装箱 与自动拆箱
- 基本数据类型、包装类—>String:调用String重载的valueOf(Xxx xxx)
- String—>基本数据类型、包装类:调用包装类的parseXxx(String s)
- 注意:转换时,可能会报NumberFormatException
(4)常用转换方法
-
字符串转换成基本数据类型
- 通过包装类的构造器实现:int a = new Integer(“123”);
- 通过包装类的parseXxx(String s)静态方法:float v = Float.parseFloat(“3434.1”);
-
基本数据类型转换成字符串
- 调用字符串重载的valueOf()方法:String s = String.valueOf(2.34f);
- 更直接的方式:String s = 3.45+""
(5)面试题
如下两个题目输出结果相同吗?各是什么:
@org.junit.Test
public void test() {
//此处三目运算符?后面两部分会涉及到类型统一、自动转换
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);//1.0
}
@org.junit.Test
public void test2(){
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);//1
}
以下面试题的考点是:数据缓存的问题IntegerCache
Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128~127范围的整数。如果使用自动装箱的方式,给Integer赋值的范围在-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的是提高效率。
@org.junit.Test
public void test() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true
Integer x = 128;//相当于new了一个对象
Integer y = 128;//相当于new了一个对象
System.out.println(x == y);//false
}
向上转型与向下转型
向上转型
向上转型:多态
向下转型
(1)为什么使用向下转型
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特的属性和方法?使用向下转型
(2)如何实现向下转型
使用强制类型转换符:()
(3)使用时的注意事项
- 使用强转时,可能出现
ClassCastException
的异常 - 为了避免在向下转型时出现
ClassCastException
的异常,我们在向下转型之前,先进行instanceof
的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型
(4)instanceof的使用
- a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false
- 如果 a instanceof A返回true,则 a instanceof B也返回true。其中,类B是类A的父类
- 要求a所属的类与类A必须是子类和父类的关系,否则编译错误
(5)对象类型转换(Casting)
- 基本数据类型的Casting
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
- 强制类型转换:可以把大的数据类型强制转换(Casting)成小的数据类型
- 对Java对象的强制类型转换称为
造型
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
(6)对象类型转换示例
public class Test {
public void method(Person e) { // 设Person类中没有getschool() 方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if (e instanceof Student) {
Student me = (Student) e; // 将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(String[] args) {
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
方法的重写
(1)什么是方法的重写
- 子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
- 在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法
(2)应用
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法
(3)重写的规则
- 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
- 子类方法抛出的异常不能大于父类被重写方法的异常
- 注意:
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)
- 因为static方法是属于类的,子类无法覆盖父类的方法
(4)方法重写示例
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
} }
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
class Parent {
public void method1() {}
}
class Child extends Parent {
//非法,子类中的method1()的访问权限private比被覆盖方法的访问权限public小
private void method1() {}
}
public class UseBoth {
public static void main(String[] args) {
Parent p1 = new Parent();
Child c1 = new Child();
p1.method1();
c1.method1();
} }
(5)面试题:区分方法的重写和重载?
要点包括:①二者的概念;②重载和重写的具体规则③重载:不表现为多态性;重写:表现为多态性
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为==“早绑定”或“静态绑定”;而对于多态,只等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”==。
子类对象实例化全过程
(1)从结果上来看:继承性
- 子类继承父类以后,就获取了父类中声明的属性或方法
- 创建子类的对象,在堆空间中,就会加载所父类中声明的属性
(2)从过程上来看
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…直到调用了java.lang.Object
类中空参的构造器为止。正因为加载过所的父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用
(3)特殊说明
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
(4)子类对象实例化示例
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}
}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}
}
输出结果:
Creature无参数的构造器
Animal带一个参数的构造器,该动物的name为灰太狼
Animal带两个参数的构造器,其age为3
Wolf无参数的构造器
关键字
被Java语言赋予了特殊含义,用作专门用途的字符串(单词),这些关键字不能用于常量、变量、和任何标识符的名称。
关键字:this
(1)this是什么
- 可以调用的结构:属性、方法;构造器
- this理解为:当前对象 或 当前正在创建的对象(其中,当前对象是指当前正在操作本方法的对象)
(2)this调用属性、方法
- 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this
- 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参
- 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量
- 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找
(3)this调用构造器
- 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器
- 构造器中不能通过"this(形参列表)"的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有n-1个构造器中使用了"this(形参列表)"
- "this(形参列表)"必须声明在类的构造器的首行
- 在类的一个构造器中,最多只能声明一个"this(形参列表)"
关键字:super
(1)super是什么
- super关键字可以理解为:父类的
- 可以用来调用的结构有:属性、方法、构造器
- super的追溯不仅限于直接父类
(2)super调用属性、方法
- 可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 当子类和父类中定义了同名的属性时,要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性
- 当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法
(3)super调用构造器
- 可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- "super(形参列表)"的使用,必须声明在子类构造器的首行
- 在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二一,不能同时出现
- 在构造器的首行,没显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
(4)super示例
class Person {
protected String name = "张三";
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool() {
return school;
}
public String getInfo() {
return super.getInfo() + "\nschool: " + school;
}
}
public class StudentTest {
public static void main(String[] args) {
Student st = new Student();
System.out.println(st.getInfo());
}
}
输出结果为:
Name: 张三
age: 0
school: New Oriental
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name);
school = s;
}
// 编译出错: no super(),系统将调用父类无参数的构造器。
public Student(String s) {
school = s;
}
}
(5)this和super的区别
维度 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
关键字:static
- 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量
-
如果想让一个类的所有实例共享数据,就用类变量
-
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法
-
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用
(1)使用范围
- 在Java类中,可用static修饰属性、方法、代码块、内部类
- 可以用来修饰的结构:主要用来修饰类的内部结构
- static:静态的
(2)被修饰后的成员具备的特点
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
- 在静态的方法内,不能使用this关键字、super关键字
(3)static修饰属性:静态变量(或类变量)
- 属性,是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的
- static修饰属性的其他说明
- 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
- 静态变量的加载要早于对象的创建
- 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
(4)static修饰方法:静态方法、类方法
- 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
- 静态方法中,只能调用静态的方法或属性
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
(5)如何判定属性和方法应该使用static关键字
- 关于属性
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的
- 类中的常量也常常声明为static
- 关于方法
- 操作静态属性的方法,通常设置为static的
- 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
(6)类变量VS实例变量内存解析
class Chinese {
String name;
int age;
static String nation;
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test{
public static void main(String[] args) {
Chinese.nation = "中国";
System.out.println(Chinese.nation);//中国
Chinese c1 = new Chinese();
System.out.println(c1);//Chinese{name='null', age=0}
c1.name = "姚明";
c1.age = 40;
System.out.println(c1);//Chinese{name='姚明', age=40}
Chinese c2 = new Chinese();
System.out.println(c2);//Chinese{name='null', age=0}
c2.name = "马龙";
c2.age = 30;
System.out.println(c2);//Chinese{name='马龙', age=30}
c1.nation = "CHN";
System.out.println(Chinese.nation);//CHN
c2.nation = "CHINA";
System.out.println(Chinese.nation);//CHINA
}
}
示例代码
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++; //非法
return total;
}
public Person() {
total++;
id = total;
}
}
public class PersonTest {
public static void main(String[] args) {
System.out.println("Number of total is " + Person.getTotalPerson());
//没有创建对象也可以访问静态方法
Person p1 = new Person();
System.out.println("Number of total is " + Person.getTotalPerson());
}
}
输出结果为:
Number of total is 0
Number of total is 1
class Person {
private int id;
public static int total = 0;
public Person() {
total++;
id = total;
}
public static void main(String args[]) {
Person Tom = new Person();
Tom.id = 0;
total = 100; // 不用创建对象就可以访问静态成员
}
}
public class StaticDemo {
public static void main(String args[]) {
Person.total = 100; // 不用创建对象就可以访问静态成员
//访问方式:类名.类属性,类名.类方法
System.out.println(Person.total);
Person c = new Person();
System.out.println(c.total); //输出101
}
}
关键字:final
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示==“最终的”==
-
final标记的类不能被继承。提高安全性,提高程序的可读性。
例如:String类、System类、StringBuffer类
-
final标记的方法不能被子类重写,比如:Object类中的getClass()
-
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次
- final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用
- final double MY_PI = 3.14
-
static final 用来修饰属性:全局常量
(1)final修饰类
final class A {
}
class B extends A{
//错误,不能被继承
}
final 用来修饰一个类:此类不能被其他类所继承。比如:String类、System类、StringBuffer类
(2)final修饰方法
class A{
public final void print(){
System.out.println("A");
}
}
class B extends A{
public final void print(){//错误,不能被重写
System.out.println("B");
}
}
final 用来修饰方法:表明此方法不可以被重写,比如:Object类中getClass()
(3)final修饰变量
class A{
private final String INFO = "htze";//声明字符常量
public void print(){
//The final field A.INFO cannot be assigned
INFO = "tj";
}
}
-
常量名要大写,内容不可修改
-
final 用来修饰变量:此时的"变量"就称为是一个常量
- final修饰属性:可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化
-
final修饰局部变量:
- 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
(4)final应用示例
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
// 可在构造器中给final修饰的“变量”赋值
ID = ++totalNumber;
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
System.out.println(I);
final int J;
J = 20;
J = 30; // 非法
}
}
public class Test {
public static void main(String[] args) {
Other o = new Other();
new Test().addOne(o);
}
public void addOne(final Other o) {
// o = new Other();
o.i++;
}
}
class Other {
public int i;
}
关键字:abstract
- abstract:抽象的
- 可以用来修饰:类、方法
(1)抽象类与抽象方法概述
- 用abstract关键字来修饰一个类,这个类叫做抽象类
- 用abstract来修饰一个方法,该方法叫做抽象方法
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:public abstract void talk();
- 含有抽象方法的类必须被声明为抽象类
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私方法、静态方法、final的方法、final的类
(2)abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
- 抽象的使用前提:继承性
(3)abstract修饰方法:抽象方法
- 抽象方法只方法的声明,没方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的
- 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
- 若子类没重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
(4)抽象类的应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类
问题描述:
- 在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离
- 卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以
解决方案
- Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类
- Vehicle是一个抽象类,有两个抽象方法
public abstract class Vehicle {
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle {
public double calcFuelEfficiency() {
//写出计算卡车的燃料效率的具体方法
}
public double calcTripDistance() {
//写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vehicle {
public double calcFuelEfficiency() {
//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance() {
//写出计算驳船行驶距离的具体方法
}
}
(5)多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
可以解决的问题
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式
示例
abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("执行时间是:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
public void code() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
//抽象类的应用:模板方法的设计模式
public class TemplateMethodTest {
public static void main(String[] args) {
BankTemplateMethod btm = new DrawMoney();
btm.process();
BankTemplateMethod btm2 = new ManageMoney();
btm2.process();
}
}
abstract class BankTemplateMethod {
// 具体方法
public void takeNumber() {
System.out.println("取号排队");
}
public abstract void transact(); // 办理具体的业务 //钩子方法
public void evaluate() {
System.out.println("反馈评分");
}
// 模板方法,把基本操作组合到一起,子类一般不能重写
public final void process() {
this.takeNumber();
this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码
this.evaluate();
}
}
class DrawMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要取款!!!");
}
}
class ManageMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要理财!我这里有2000万美元!!");
}
}
应用场景
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate、HibernateTemplate等
关键字:interface
(1)接口的概述
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 “能不能”
的关系。 - 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
- 接口(interface)是抽象方法和常量值定义的集合
(2)接口的特点
-
用interface来定义
-
接口中的所有成员变量都默认是由
public static final
修饰的 -
接口中的所有抽象方法都默认是由
public abstract
修饰的 -
接口中没有构造器
-
接口采用多继承机制
-
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
-
一个类可以实现多个接口,接口也可以继承其它接口
-
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类
-
接口的主要用途就是被实现类实现。(面向接口编程)
-
与继承关系类似,接口与实现类之间存在多态性
-
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义==(JDK7.0及之前)==,而没有变量和方法的实现
- JDK7及以前,只能定义全局常量和抽象方法
- 全局常量:
public static final
的,但是书写时,可以省略不写 - 抽象方法:
public statract
- 全局常量:
- JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
- JDK7及以前,只能定义全局常量和抽象方法
-
接口中不能定义构造器,意味着接口不可以实例化
(3)说明
-
Java开发中,接口通过让类去实现(implements)的方式来使用
- 如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
- 如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类
-
Java类可以实现多个接口 —>弥补了Java单继承性的局限性
格式:
class AA extends BB implements CC,DD,EE
-
接口与接口之间可以继承,而且可以多继承
-
接口使用上满足多态性
-
接口,实际上就是定义了一种规范
(4)接口应用示例
class Computer {
public void transferData(USB usb) {//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB {
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB {
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB {
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
(5)Java8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
-
静态方法:使用
static
关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中
找到像Collection/Collections或者Path/Paths这样成对的接口和类。 -
默认方法:默认方法使用
default
关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
JDK8接口新规范知识点汇总
- 接口中定义的静态方法,只能通过接口来调用
- 通过实现类的对象,可以调用接口中的默认方法
- 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法(类优先原则)
- 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。–>接口冲突。这就必须在实现类中重写此方法
关键字:import
- 在源文件中显式的使用import结构导入指定包下的类、接口
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,则并列写出即可
- 可以使用"xxx."的方式,表示可以导入xxx包下的所结构
- 如果使用的类或接口是java.lang包下定义的,则可以省略import语句
- 如果使用的类或接口是本包下定义的,则可以省略import结构
- 如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示
- 使用"xxx.*"方式表明可以调用xxx包下的所结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
- import static:导入指定类或接口中的静态结构:属性或方法
设计模式
单例设计模式(Singleton)
(1)概述
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
(2)要解决的问题
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
(3)单例设计模式的优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
(4)单例设计模式-应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
(5)单例设计模式-代码实现
单例设计模式——饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
单例设计模式——懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if (single == null) {
synchronized (Singleton.class){
if (single == null) {
single = new Singleton();
}
}
}
return single;
}
}
(6)单例设计模式两种创建方式对比
- 饿汉式
- 坏处:==对象加载时间过长
- 好处:饿汉式是线程安全的
- 懒汉式
- 延迟对象的创建
代理模式(Proxy)
(1)概述
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
(2)代理模式示例
public class StaticProxyTest {
public static void main(String[] args) {
Star s = new Proxy(new RealStar());
s.confer();
s.signContract();
s.bookTicket();
s.sing();
s.collectMoney();
}
}
interface Star {
void confer();// 面谈
void signContract();// 签合同
void bookTicket();// 订票
void sing();// 唱歌
void collectMoney();// 收钱
}
class RealStar implements Star {
public void confer() {
}
public void signContract() {
}
public void bookTicket() {
}
public void sing() {
System.out.println("明星:歌唱~~~");
}
public void collectMoney() {
}
}
class Proxy implements Star {
private Star real;
public Proxy(Star real) {
this.real = real;
}
public void confer() {
System.out.println("经纪人面谈");
}
public void signContract() {
System.out.println("经纪人签合同");
}
public void bookTicket() {
System.out.println("经纪人订票");
}
public void sing() {
real.sing();
}
public void collectMoney() {
System.out.println("经纪人收钱");
}
}
(3)应用场景
- 安全代理:屏蔽对真实角色的直接访问
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
- 比如要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开
(4)分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)—JDK自带的动态代理,需要反射等知识
工厂模式
(1)解决的问题
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的
(2)具体模式
- 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)