Java基础 —— 面向对象
记录一下这些天学习的一些关于JavaSE的基础, 第二篇 —— 面向对象篇。(知识的搬运工)
文章目录
一、面向过程与面向对象
-
面向过程(POP),强调的是功能行为,以函数为最小单位,考虑怎么做。
-
面向对象(OOP),将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
面向对象更强调运用人类在日常的思维逻辑中采用的思想方法和原则,如抽象、分类、继承、聚合、多态等。
例 :人把大象装进冰箱
-
面向过程
1. 把冰箱打开 2. 把大象塞进冰箱 3. 关闭冰箱门
-
面向对象
人{
打开(冰箱) {
冰箱.开开();
}
抬起(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.闭合();
}
}
冰箱{
开开();
闭合();
}
大象{
进入(冰箱){}
}
二、Java类及类的成员
属性、方法、构造器、代码块、内部类
-
类和对象
- 类:是对 一类事物的描述 ,是 抽象的 、概念上的定义
- 对象: 实际存在 的该类事物的每个个体,因而 也称为 实例
-
类的成员
-
属性:对应类中的成员变量, 需要修饰符(可缺省)
public class Person{ private int age; //声明 private 变量 age public String name = “Lila”; //声明 public 变量 name }
区别于局部变量,在方法体外,类体内声明的变量称为成员变量,在方法体内部声明的变量称为局部变量。局部变量在栈空间中,没有初始化值;成员变量在堆空间或静态域内,有初始化值。
-
行为:对应类中的成员方法
- 方法是类或对象行为特征的抽象,用来完成某个功能操作
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里
-
-
类和对象的使用
- 创建类,设计类的成员(属性、方法)
- 创建类的对象
- 调用对象结构
public class OOPTes {
public static void main(String[] args) {
//创建Person类对象
Person p1 = new Person();
//调用对象结构:属性、方法
p1.name = "Tom";
p1.isMale = true;
System.out.println(p1.name);
p1.rap();
//实例化对象
Person p2 = new Person();
System.out.println(p2.name); //这里对象会存在默认初始化值
//没有创建实例化对象,而是指向了同一个地址
Person p3 = p1;
System.out.println(p3.name); //Tom
p3.age = 10;
System.out.println(p1.age); //10
}
}
//类
class Person {
//属性
String name;
int age = 28;
boolean isMale;
//方法
public void eat() {
System.out.println("吃饭");
}
public void rap() {
System.out.println("练习时长两年半");
}
}
-
对象的创建----内存解析
- 堆Heap : 此内存区域的唯一目的就是 存放对象实例,几乎所有的对象实例都在这分配内存 。 这一点在Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配 。
- 栈 Stack 是指虚拟机栈 。 虚拟机栈用于 存储 局部变量 等 。局部变量表存放了编译期可知长度的各种基本数据类型 boolean 、 byte 、char 、 short 、 int 、 float 、 long 、double 、 对象引用(它不等同于对象本身,是对象在堆内存的首地址) 。 方法执行完,自动释放。
- 方法区 Method Area 用于 存储已被虚拟机加载的 类信息 、 常量 、 静态变量 、 即时编译器编译后的代码 等数据 。
三、构造器(构造方法)
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为 void 不同)
- 不能被 static 、 final 、 synchronized 、 abstract 、 native 修饰,不能有return 语句返回 值
构造器的作用: 创建对象;给对象进行初始化
public class Animal {
private int legs
//构造器
public Animal() {
legs= 4
}
public Animal(int n) {
legs= n
}
}
Animal = new Animal(3); // new + 构造器
- 根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
注 意:
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
四、面向对象的三大特征
封装性、继承性、多态性、(抽象性)
1. 封装
-
为什么需要封装
使用者对类内部定义的属性对象的成员变量 的直接操作会导致数据的错误、混乱或安全性问题。
-
程序设计追求“高内聚,低耦合”
- 高内聚 :类 的内部数据 操作细节自己完成,不允许外部干涉;
- 低耦合: 仅对外暴露少量的 方法 用于 使用
-
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性 , 这就是封装性的设计思想。
-
体现
- 将数据声明为private, 再提供public方法: getXxx 和 setXxx 实现对该属性的操作.
- 不对外暴露私有方法
- 单例模式 (将构造器私有化)
- 如果不希望类在包外被调用,可以将类设置为缺省的
-
访问权限修饰符
提示:修饰类只能使用缺省、public
2. 继承(extends)
-
为什么要继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。(此处的多个类称为 子类 (派生类),单独的这个类称为 父类 (基 类或 超类)。
-
作用
- 减少代码冗余,提高 代码复用性
- 更有利于功能的扩展
- 让类与类之间产生了 关系 ,提供多态的 前提
-
规则
-
子类不能直接访问父类中私有的 (private)的成员变量和方法
-
只支持 单 继承和多层继承 , 不允许多重继承, 即一个子类只能有一个直接父类,一个父类可派生多个子类(可对应现实的父与子)
-
所有java类(除本身)都直接或间接继承于java.lang.Object类
-
特别的, 父类中声明为private的属性和方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的结构。
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {}
}
/*Student 类继承了父类 Person 的所有属性和方法,并增加了一个属性 school 。 Person 中的属性和方法 ,Student 都可以使用 */
class Student extends Person {
public String school;
}
3. 多态
-
父类的引用指向子类的对象,可直接应用在抽象类和接口上
-
多态性的产生
- Java 引用变量有两个类型: 编译时类型 和 运行时类型 。 编译时类型由声明该变量时使用的类型决定 ,运行时类型由实际赋给该变量的对象 决定 。 简称: 编译 时 看左边;运行时 看右边
- 若编译时类型和运行时类型不一致, 就出现了对象的多态性 (Ploymorphism)
- 多态情况下, “看左边”: 看的是父类的引用 (父类中不具备子类特有的方法)
“看右边”: 看的是子类的对象( 实际运行的是子类重写父类的方法)
//对象的多态性,左边Person是父类,右边是子类Men Person p = new Men(); //父类的引用指向子类的对象
-
多态的使用
- 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就 不能 再访问子类中特有的属性和方法,想要调用则需转型。
//当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法----- 虚拟方法调用 p.eat(); //子类特有方法earn(),调用时报错没有定义该方法,编译时看左边,即Person类,而不是Man类 p.earn();
例:招聘人员,可能招到男生或女生,招到男(女)生都会做其对应的工作。
-
虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。类似于动态绑定的效果
前提:父类中定义了 该方法,各个子类重写了该方法。
执行:多态的情况下,调用对象的 该方法,实际执行的是子类重写的方法
举例
//这是一个连接数据库的方法,由于数据库有mysql,Oracle等多种源,所以需要定义为它们的父类Connection,实际上传的参数可以是connection = new MySQLConnection();而不需要为每个连接源定义一个方法。即使传入不同的子类对象,也能够使用同一个方法进行操作。
public void doData(Connection connection) {
//定义通过步骤
}
对象的多态性只适用于方法,不适用与属性
例:
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();
Base b = s;//多态性
System.out.println( b == s);// true, "==":比较引用类型是比较地址
System.out.println( b.count);//10, 多态性只适用于方法,不适用于属性
b.display();//20 , 适用于方法,子类方法中重写了
}
}
-
从编译和运行的角度上看
重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数 表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成 了不同的方法。 它们 的调用地址在编译 期就 绑定了 。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为 “早绑定”或“静态绑定
而对于多态,只有等到方法调用的那一刻 解释运行 器 才会确定所要调用的具体方法,这称为 “晚绑定”或“动态绑定 。
-
多态的作用:提高了代码的通用性,常称作接口重用
补充:
-
**instanceof **操作符
-
x instanceof A :检验 x 是否为类 A 的对象,返回值为 boolean 型,一般转型之前使用。
-
要求 x 所属的类与类 A 必须是子类和父类的关系(直接或间接),否则编译错误。
-
-
对象类型转换(Casting)
-
基本数据类型的 Casting
-
自动类型转换 :小的数据类型可以自动转换成大的数据类型
如 long g= 20; double d= 12.0 f
-
强制类型转换: 可以把大的数据类型强制转换成小的数据类型
如 float f= (float) 12.0; int a= (int) 1200 L
-
-
对 Java 对象的强制类型转换称为造型
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过造型 强制类型转换 实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用 instanceof 操作符测试一个对象的类型
String objStr = (String) obj;
Object objPri = new Integer(5); //多态
//运行时引发 ClassCastException 异常,非法
String str = (String) objPri;
转型时需要是子父类关系
五、子类对象实例化过程
- 从结果上看(封装性)
- 子类继承父类后,就获取了父类中声明的属性和方法
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从结果上看
- 当通过子类构造器创建子类对象时,一定会直接或间接调用其父类的构造器,进而调用父类的父类构造器,直接调用Objective类中的空参构造器为止,从而加载所有父类的结构。
注:只创建了一个new的对象,其他只调用了构造器
方格表示构造器
六、Object类
-
Object是所有java类的父类(不需要使用extends指明,默认)
方法名称 类型 描述 1 public Object() 构造 构造器 2 public boolean equals(Object obj) 普通 对象比较 3 public int hashCode() 普通 取得 Hash 码 4 public String toString() 普通 对象打印时调用 …
1. == 与 equals方法
-
==:基本类型比较值,引用类型比较地址(是否指向同一个对象)
-
equals 方法,所有类都继承了Object类,可重写equals方法, 没有重写等同于 “==”。
public boolean equals(Object obj) { return (this == obj); }
-
当比较引用类型,作用等同于 “ == ” , 用于比较地址,obj1.equals(obj2)
注: 当用 equals() 方法进行比较时 对类 File 、 String 、 Date 及包装类来说 是比较类型及内容而不考虑引用的是否是同一个对象 , 因为在这些类中已重写equals方法。
-
当自定义使用 equals() 时,可以重写 。 用于比较两个对象的 内容 是否都相等
-
任何情况下 x.equals(null) 永远返回是 false
-
//String类中重写的equals方法
public boolean equals(Object anObject) {
if (this == anObject) { //先判断地址是否一样,地址一样说明内容一定一样
return true;
}
if (anObject instanceof String) { // 判断是否是String类型再比较内容
String anotherString = (String)anObject;
int n = value.length; //当前字符串的长度
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
2. toString()方法
- toString() 方法在 Object 类中定义 其返回值是 String 类型, 返回类名和它的引用地址
//Object类toString() 方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 在 进行 String 与其它类型数据的连接操作时, 自动调用 toString 方法
Date now=new Date()
System.out.println ("now="+ now); //相当于System.out.println (“now=" + now.toString);
- 可以根据需要在用户自定义类型中重写 toString() 方法
如String 类重写了 toString() 方法 返回字符串的值 。 - 基本类型数据转换为 String 类型时 调用了对应包装类的 toString() 方法
注: print 实际调用了toString()方法
String = null;
System.out.println(s) // null,内部有一个判断是否为空的保护机制
System.out.println(s.toString()); //NullPointerException
七、包装类的使用
基本数据类型封装后有了类的特征,体现面向对象的思想
-
基本数据类型包装成包装类的实例 —— 装箱
- 通过包装类的构造器实现:
int i = 500; Integer t = new Integer(i);
装箱:包装类使得一个基本数据类型的数据变成了类, 有了类的特点,可以调用类中的方法。
- 通过包装类的构造器实现:
-
获得包装类对象中包装的基本类型变量 —— 拆箱
-
调用包装类的 xxxValue 方法:
boolean b = bObj.booleanValue();
-
JDK1.5 之后,支持自动装箱,自动拆箱。但类型必须匹配。
-
-
包装类的 parseXxx (String s) 静态方法可将字符串装换为基本数据类型
Float f = Float.parseFloat ("12.1");
-
字符串重载的 valueOf() 方法可将基本数据类型转换成字符串
String fstr = String.valueOf (2.34f);
注:包装类的默认值为null
面试题
//三目运算比较的是基本类型,在编译时自动拆箱为int,double。同时三目运算要求两种类型一致,所以int自动被提升为double,然后自动装箱为Object类
Object o1 = true ? new Integer(1) : new Double(2.0);
//输出时调用了toString()方法
System.out.println(o1); //1.0
注:Integer内部定义了IntegerCache结构,其中定义了Integer[], 保存了 -128 ~ 127范围内的整数,如果使用自动装箱的方法在该范围内的数,可以直接使用数组中的元素,不需要自己new,从而提高效率
八、方法
1. 方法的重载
-
概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
-
特点: 与返回值类型无关,只看参数列表,且参数列表必须不同。参数个数或参数类型 。调用时, 根据方法参数列表的不同来区别。
-
重载规则:
- 方法名称必须相同
- 参数列表必须不同(个数或类型、排列顺序等不同)
- 方法的返回类型可相同也可不同
-
实现理论:
- 方法名称相同时,编译器会根据调用方法的参数个数、类型去逐个匹配,已选择对应的方法,如果匹配失败,则编译器报错。
2. 可变参数
- 在方法声明中,在指定参数类型后加一个省略号(…)
- 一个方法只能指定一个可变参数,他必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
public void test (int x, int... i) {}
3. 方法参数的值传递机制
- 形参:方法定义时,声明在小括号内的参数
- **实参:**方法调用时实际传给形参的参数值
Java里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受 影响
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
4. get、set方法
为什么要用get 、 set方法,而不直接使用public
在类的设计中,会定义private变量,然后通过get()、set()方法控制变量,那为什么要用get 、 set方法,而不直接使用public呢?
- get()、set()方法内部可定义自己的逻辑,而不仅仅是访问和赋值(public的效果)。我们可实现自己内部逻辑,如字符串拼接,计算,判断等,更加具有灵活性和健壮性。
- 封装思想,尽量隐藏类中的成员变量,只提供使用接口,类似于代码规范。
5. 方法的重写(override)
- 定义:在子类中可以根据需要对从父类中继承来的方法进行改造 也称为方法 的 重置、覆盖。 在执行过程中,子类方法会覆盖父类方法。
- 要求
- 子类重写的方法 必须 和父类被重写的方法 具有相同的 方法名、 参数 列表
- 子类重写的方法的返回值类型 不能大于 父类被重写的方法的返回值类型
- 父类方法返回类型为A类,则子类重写方法返回类型为A类或A的子类
- 父类方法返回类型为基本类型,则子类重写方法返回类型为基本类型
- 子类重写的方法使用的访问权限 不能小于 父类被重写的方法的访问权限
- 子类不能重写父类中声明为 private 权限的方法
- 子 类方法抛出的异常类型不能大于父类被重写方法 的异常类型
子类与父类中同名同参数的方法必须同时声明为非 static 的 (即为 重写 ),或者同时声明 为static 的 (不是 重写 ,因为 static 方法是属于类的,静态方法在一开始就会被加载)
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 + "\nnage : "+ age+ "school : "+ school;
}
}
Person p1=new Person();
p1.getInfo(); //调用 Person 类的 getInfo 方法
Student s1=new Student();
s1.getInfo(); //调用 Student 类的 getInfo 方法
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub();
//这里调用的是应该是子类中重写的方法,所以输出为sub_1
base.add(1, 2, 3);
Sub sub = (Sub) base;
base.add(1,2,3) //这里调用的的是类中确定性方法,输出sub_2
}
}
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");
}
}
6. main方法的使用
- 由于 Java 虚拟机需要调用类的 main() 方法,所以该方法的访问权限必须是public。
- 因为 Java 虚拟机在执行 main() 方法时不必创建对象,所以该方法必须是 static 的。
- 该方法接收一个 String 类型的数组参数,该数组中保存 执行 Java 命令时传递给所运行的类的参数。
//程序入口、静态方法
public static void main(String[] args ){
}
九、其他关键字
this、super、static、final、abstract、interface、package、import等
1. this关键字
-
在方法内部使用,即这个方法所属对象的引用
-
在构造器内部使用,表示该构造器正在初始化的对象,
-
当 形参与成员变量同名时,若在方法或构造器内需使用成员变量,必须添加 this 表明该变量是类的成员变量
-
使用 this 访问属性和方法时,如果在本类中未找到,会从父类中查找
-
this(形参)必须声明在当前构造器的首行,且最多声明一个
this可理解为当前实例对象 或正在创建的对象
class Person{
Person{// 定义 Person 类
private String name ;
private int age ;
public Person(){
System.out.println("新对象实例化 ")
}
public Person(String name,int age){
this(); // 调用本类中的无 参构造器
this.name = name ; //不使用this时,会存在就近原则,导致无法找到name成员变量
this.age = age ;
}
public void getInfo(){
System.out.println("姓名 :" + name);
this.speak();
}
public void speak(){
System.out.println("年龄: :" + this.age);
}
}
2. Package 、 import关键字
- package 顶层包名.子包名;
包的作用:
- 帮助管理大型软件 系统,将功能 相近的类划分到同一个包中,如MVC的设计模式
- 解决类命名冲突
- 控制访问权限
3. super关键字
使用 super 来调用父类中的指定操作:
- 可用于访问父类中定义的属性、调用父类中定义的成员方法
- 可用于在子 类 构造 器中调用父类的 构造 器
注:1. 当子父类出现同名成员时,可以用 super 表明调用的是父类中的成员,没有显示使用则调用子类成员(就近原则);
2. super不仅限于直接父类,也可用于间接父类,所以调用父类中的重写方法时,需要显示使用super
3. 类似于this,this表示本类对象的引用,**super表示父类的内存空间标识 **,这里我理解为存放了父类的内存地址。
关于调用父类构造器
- 若子类构造器中没有显示调用父类构造器,则子类中所有的 构造 器 默认 会访问父类中 空参数 的 构造 器
- 当父类中没有空参数的构造器时,子类的构造器必须通过 **this( 参数列表 )**或者 super( 参数列表) 语句指定调用本类或者父类中相应的构造 器 。 同时 只能 二选一 且 必须 放在构造 器 的 首 行(加载顺序问题,保证先初始化父类)
- 如果子类构造器中既未显式调用父类或本类的构造器 且父类中又没有无参的构造器 则 编译出错
- 在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器
例:
public class Person {
private String name;
private int age;
public Person(String name , int age ){
this(name , age , null);
}
public String getInfo() {
return "Name: "+ name + "\n age: "+ age;
}
}
public class Student extends Person {
private String school;
public Student(String name , int age , String s ){
super(name , age); //显示调用父类构造器
school = s;
}
//编译出错 : no super() 系统将调用父类无参数的构造器。
public Student(String s ) {
//super(); //没有显示this和super时,默认有这个空参构造器
school = s;
}
//@Override 表示这是一个重写方法
@Override
public String getInfo() {
//调用父类成员方法
return super.getInfo()+ "\nschool: " + school;
}
}
this和super的区别
区别点 | this | super | |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
4. static关键字
如果想让一个类的所有实例共享数据,就用类变量!不因对象的不同而改变
由于不需要创建对象就可以调用类方法 ,从而简化了方法的调用。
- 在 Java 类中 可用 static 修饰 属性 、 方法 、 代码块 、 内部类
- 被修饰后的成员具备以下特点:
- 随着类的加载而加载,加载时早于对象的创建,只加载一次
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
- this,super都是在对象创建后,所以static修饰后的方法无法使用,也不能重写。
- 静态方法中,只能调用静态属性或方法
- 如何确定一个属性是否需要static声明
- 属性可以被多个对象所共享(共有的、固定的特征)
- 类中的常量
- 如何确定一个方法是否需要static声明
- 操作静态属性的方法,通常声明为static
- 工具类的方法,习惯上声明为static,方面调用, 如 Math,Arrays等
class Person{
public static int total = 0; // 可直接访问 Person.total,共享
}
System.out.println(); //out即static修饰,所以能直接调用
静态方法或属性在类加载时被加载到方法区,早于对象创建(生命周期角度),理解这句话就够了
5. final关键字
- final 标记的类不能被继承 。 提高安全性 提高程序的可读性 。
String 类 、 System 类 、 StringBuffer 类
-
final 标记的方法不能被子类重写。
Object的 getClass()方法
-
final 标记的变量 成员变量或局部变量 即称为常量 。 名称大写 且只能被赋值一次,不能被更改,必须手动初始化,通常和static一起 。
赋值位置:显示初始化、代码块中初始化、构造器中初始化
final int WIDTH = 0; final int PI; final int LEFT; { PI = 3.14; } public FinalTest(int m){ LEFT = m; }
十、代码块
作用:对 Java 类或对象进行 初始化
- 一个类中代码块若有修饰符 则只能被 static 修饰 ,称为 静态代码块(static block) ;没有使用 static 修饰的 为 非静态代码块。
- 静态代码块:用 static 修饰的代码块
- 可以 对类的属性、类的声明进行初始化操作,且不可以调用非静态 的 属性 和方法。
- 若 有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态 代码 块随着类的加载而加载,且只执行 一 次。
- 非静态代码块:没有 static 修饰的代码块
- 除了调用非静态的结构外, 还可以调用 静态的变量或方法。
- 每次 创建对象的时候 都会执行一次 。 且先于构造 器 执行。
class Person {
public static int total;
//静态代码块,类加载时加载并执行,只执行一次
static {
total = 100; //为 total 赋初值
System.out.println("in static");
}
//非静态代码块,随着对象实例的创建而执行,每创建一次,执行一次
{
total1 = 100;
}
…… //其它属性或方法声明
}
总结:程序中成员变量赋值的执行顺序,由父及子,静态先行
十一、抽象类和抽象方法
父类:更一般、更通用
子类:更具体
-
类的设计应该保证父类和子类能够共享特征。当一个父类设计得非常抽象( 没有具体的实例),这样的类叫做抽象类, 用abstract关键字修饰。
-
抽象方法 :只有方法的声明,没有方法的实现。以分号结束; 且含有抽象方法 的类必须被声明为抽象类。
public abstract void talk();
-
抽象 类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
abstract class A {
abstract void m1();
public void m2() {
System.out.println("A 类中定义的 m2 方法");
}
}
class B extends A {
void m1() {
System.out.println("B 类中定义的 m1 方法");
}
}
- 抽象类应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
//例:
//定义交通工具类Vehicle类,包括两个子类Truck类和RiverBarge类,由于两种工具计算燃料效率和行驶距离的方法不一致,无法在父类提供计算方法,只能由各个子类去实现,所以要使用抽象类。
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 () {
//写出计算驳船行驶距离的具体方法
}
}
//创建匿名子类
Person p = new Person() { // Person是父类
@Override
public void eat(){
//重写的方法
}
}
十二、接口(interface)
接口 就是规范,定义的是一组规则,体现了现实世界中“如果你是 要 则必须能 …”的思想。 继承是一个 是不是 的关系,而接口实现则是 能不能的关系。
如数据库的操作,定义了各种规范,只提供抽象的方法,定义了大部分接口功能,使用时自己实现即可。
-
java不支持多继承,通过接口,实现多继承效果
-
从多个类中抽取出一些共同的特征
class SubClass extends SuperClass implements InterfaceA{
//接口中只能定义全局常量
public static final int MIN = 1;
int MAX = 9; //这里省略了public static final,但仍是全局常量 , 等同于public static final int MAX = 9;
//抽象方法
public abstract void fly();
//省略public abstract
void stop(); //同上
}
- 一 个类可以实现多个接口 接口也可以继承其它 接口
- 接口不能定义构造器,不能实例化
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。 (面向接口编程 )
interface Runner { public void run();}
interface Swimmer {public double swim();}
class Creator{
public int eat(){…}
}
class Man extends Creator implements Runner ,Swimmer{
public void run() {
//……
}
public double swim() {
//……
}
public int eat() {
//……
}
}
- 接口之间可以多继承
interface AA {...}
interface BB {...}
interface CC extends AA,BB {...}
个人理解通常父类和子类存在某种所属关系,如猫科动物和狮子。而接口一般可以定义某种功能,如猫、狗都会跑(说的有点狭义,但可以这么用)
十三、内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内 部类。
-
在 Java 中,允许一个类的定义位于另一个类的内部,前者称为 内部类 ,后者称为 外部类
-
分类
- 成员 内部类 static 成员内部类和非 static 成员内部类)
- 局部内部类(不谈修饰符)、匿名内部类
-
内部类可以声明为private或protected, 可调用外部类的结构;若声明为static,则只能使用外部类中static的成员变量。
-
可以在内部定义属性 、 方法 、 构造器等结构
-
可以 声明为 abstract 类 因此可以被其它的内 部类 继承、可以声明为 final
class Outer {
private int s;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println("在内部类 Inner 中 s=" + s); //局部变量
System.out.println(this.s);// 内部类对象的属性s
System.out.println( Outer.this.s ); // 外部类对象属性s
}
}
public static class InnerStatic {
//....
}
public void ma() {
Inner i = new Inner(); // 使用内部类
i.mb();
}
}
public class InnerTest{
public static void main(String args []){
Outer o = new Outer();//外部类
o.ma();
Outer.Inner b = O.new Inner();//创建非静态的内部类实施方法
b.mb(333);
Outer.InnerStatic c = new Outer.InnerStatic();//创建静态内部类
}
}
单例设计模式
设计模式 是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式(类似于下棋有棋谱,就是套路)
-
所谓 类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类 的 构造 器 的 访问权限设置为 private ,这样,就不能用 new 操作符在类的外部产生
类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能 调用该类的某个静态方法 以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的 该类对象的变量也必须定义成静态的 。
//饿汉式 线程安全
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 = null;
//3. 提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null ){
single = new Singleton();
}
return single;
}
}
-
单例模式的优点:减少了系统性能开销
-
应用场景:网站 的计数器、应用程序 的日志应用、数据库连接池等
汇总图
补充
MVC设计模式
MVC设计模式将整个程序分为三个层次: 视图模型层,控制器层,与数据模型层。 这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
-
模型层 model 主要处理数据
数据对象封装 model.bean/domain
数据库操作类 model.dao
数据库 model.db
-
控制层 controller 处理业务逻辑
应用界面相关 controller.activity
存放 fragment controller.fragment
显示列表的适配器 controller.adapter
服务相关的 controller.service
抽取的基类 controller.base
-
视图层 view 显示数据
相关工具类 view.utils
自定义 view view.ui
代理模式
使用代理对象来控制对真实对象(real object)的访问,这样就可以在屏蔽直接访问原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
如:明星和经纪人的关系即可看做代理和被代理对象
public class ProxyTest {
public static void main(String[] args ){
Star star = new ProxyStar( new RealStar());
star.contract(); // 经纪人控制
}
}
//定义接口
interface Star {
public void contract();
}
//被代理类 (明星)
class RealStar implements Star{
@Override
public void contract() {
System.out.println("真正的明星在这") ;
}
}
//代理类 (经纪人)
class ProxyStar implements Star {
private Star star;
public ProxyStar(Star star ){
this.star = star;
}
public void check() {
System.out.println("经纪人检查合同");
}
public void contract() {
check();
star.contract();//经纪人先检查合同是否有误,在开始签约
}
}
JavaBean
javaBean 是一种 Java 语言写成的可重用组件,,是指符合如下标准的 Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
UML类图
- banking : 包名
- Account : 类名
- “:” : 左边是属性名,右边是类型
- 下划线表示为构造器
- +表示 public 类型, - 表示 private 类型,# 表示 protected 类型
- 方法:方法的类型(+ 、-) 方法名(参数名: 参数类型 ):返回值类型