面向对象
面向对象基础
面向对象概述
- 面向过程关注的是执行的过程,面向对象关注的是具备功能的对象
- 面向过程到面向对象,是从执行者思想,到指挥者思想的转变
三大思想
- OOA——面向对象分析(analysis)
- OOD——面向对象设计(design)
- OOP——面向对象程序(programming)
三大特征
- 封装:所有的内容对外部不可见
- 继承:将功能继承下来,继续发展
- 多态:方法的重载,本身就是一个多态性体现
类编写coding规范
- 类必须写在.java文件中
- 一个.java文件中,可以存在有N个类,但只能有一个public修饰的类
- .java文件的文件名,必须与public修饰的类名,完全一致
对象创造与内存——栈
- java中,一个线程(任务),对应一个栈(stack),每一个栈都是私有的,不被其他栈访问
- 先进后出
- 栈中的数据大小与生存期,都是确定的,缺乏灵活性;
- 但栈的存取速度比堆快,仅次于CPU中的寄存器
栈存取速度快的原因是:
- 栈内存,通过"栈指针"创建空间与释放空间——指针向下移动会创建新的内存,向上移动会释放;
- 这种移动方式,每次必须明确移动的大小与范围,以引导指针的移动,体现出来的特点就是数据大小是固定的;
- 因此更大的数据,被存储在堆内存中
对象的创造与内存——堆
- 堆中存储类的具体对象
- 先进先出
- 通过new关键字,明确告知JVM,需要一块内存空间,存储该对象;
- 与栈内存相比,堆内存的优点在于创造对象时,不必关注需要开辟多少空间,也不需要关注内存占用时长;
- 堆内存中的内存释放是通过GC完成的,当某个对象不存在引用时,则其会等待GC进行回收
构造方法
- 常见的 person p=new person() ,右侧的形式person(),与方法类似,这种特殊的方法称为构造方法
- 构造方法,作用用于对象初始化
- 一个类至少有一个,或者大于一个的构造函数,如果class中没有专门编写构造方法,那么编译的时候会自动形成一个无参构造方法
- 如果写了任意一个构造方法,则不会再生成无参构造方法,建议不要对编译器自动生成的构造方法形成依赖,在编写class的时候,建议提供两个构造方法,一个无参构造方法,一个全参构造方法
- 构造方法没有返回值类型
- 构造方法名称,与类名称一致
- 构造方法一般用于初始化
方法的重载
-
一个class中的两个方法,如果名称相同,则称为方法的重载
-
参数列表可能不同(长度、类型都可能不同)
-
注意:与返回值类型无关!!!!
-
例子如下:

匿名对象
-
如果我们在堆内存中new了一个对象出来,但是在栈内存中并没有声明其引用,此时我们称之为匿名对象
-
由于匿名对象使用后马上被GC了,因此只会存在一次,一般工具类我们会用这种形式
-
例子如下

-
如果一个对象,想使用2次或以上,一定要创建对象名。
面向对象进阶
封装性
- 隐藏对象的属性和实现细节,近对外公开访问方法,并且控制访问级别
this
如果在某一个构造函数A中,通过this调用另外一个构造函数B,则必须把B写在A方法的第一行,否则会报错(原因:如果在B前面写了其他代码,此时整个对象并没有构造完毕)。
static
-
static修饰的属性和方法,伴随着class的生成而加载,与具体的对象无关,在class加载时就会在方法区中生成初始化。

-
无论class下面new了多少个对象,static修饰的属性,内存中只保存了一份。
-
在访问时候,静态资源不能访问非静态,非静态的资源或方法可以访问静态的!(由于静态生成的较早,不依赖于具体的对象)
包
- 通常由多个单词组成,所有单词均小写,单词与单词之间用.隔开
- 一般命名为:com.公司名.项目名.模块名…
权限修饰符
| 修饰符 | 类 | 包 | 子类 | 其他包 |
|---|---|---|---|---|
| public | y | y | y | y |
| protected | y | y | y | n |
| default | y | y | n | n |
| private | y | n | n | n |
面向对象高级
继承及其内存化
-
使用extend进行继承
-
java中只有单继承、多重继承(A继承B,B继承C),没有多继承(没有A继承B,同时A继承C)
-
子类实例化的时候,会自动new一个父类的对象,该父类对象完全生成后,才进行子类对象的生成
-
在子类对象生成过程中,自动添加一个super属性,该属性指向其父类对象的内存地址
-
在代码中,调用子类对象的属性和方法时,会首先看该子类对象本身特有的,如果子类对象存在这个属性/方法,则直接调用(方法的重写),否则会去调用.super.属性/方法
+ 子类实例化时,自动创建的父类对象,此时调用的是父类的无参构造方法。如果将对应的父类的无参构造取消,此时子类的extend继承时,会报错。如果需要在继承时候,子类构造需要调用父类的指定某个构造,则可以在子类构造方法中,使用super(),调用父类指定的构造。 -
在某个类的构造中,this.()调用构造方法和super()调用构造方法都需要写在第一行,但是不能同时出现,因为同时出现不符合逻辑。

-
在子类代码中,super就表示被自动创建的父类对象。
构造代码块

静态代码块

- 构造方法,构造代码块,静态代码块的执行顺序(从先到后:静态代码块、构造代码块、构造方法)
final关键字
final用于修饰属性、变量
- final对变量进行修饰,则变量变为常量,无法对其再次进行赋值
- final修饰的局部变量,只能赋值一次(可以先声明,后赋值,但即使这样,也只能赋值一次)
- final修饰的成员属性,必须在声明时赋值。
- 在工具类中声明全局常量时候,需要的格式是:
public static final
- 注意,常量的命名规范:由1个或者多个单词组成,单词与单词之间必须使用下划线隔开,例如:SQL_INSERT
final修饰的类
- 不能被继承
final修饰的方法
- 不能被子类重写override
重写override与重载overload
重写与重载定义
- 重写:子类写了一个方法,该方法父类也有一个同名,同参数,同返回值的方法,称为重写。重写后,子类对象调用该方法后,父类的方法不再执行
- 注意:static的方法,不能重写,因static是属于类的,和对象没有关系,而重写和继承是针对对象的。
- 注意:private的方法,不能重写,因为子类对象压根没有继承到。
重写override:
- 参数列表必须完全与被重写方法相同;
- 返回类型必须完全与被重写方法相同;
- 访问权限不能比父类中被重写的权限更低;
- 父类的成员方法只能被它的子类重写;
- 声明为static和private的方法不能被重写,但是能够被再次声明
- 重写是针对对象的,对于类(也就是static修饰的静态方法),重写不生效,从语义就可以看出static、final、private方法本身都是编译期绑定的(也叫前期绑定)这些方法不存在多态,他们是在还没有运行的时候,程序在编译器里面就知道该调用哪个类的哪个方法了,而其他可观察的普通方法的绑定是在运行的时候根据具体的对象决定的。
- 子类没有继承父类的private方法,因此无法被重写,但是可以通过super调用
重写override与重载overload的区别
- 发生的位置:重写发生在子父类中,重载发生在同一个类中
- 参数列表限制:重写必须相同,重载必须不同
- 返回值类型:重写必须相同,重载无要求
- 访问权限:重写时候,子类的权限必须等于或者宽松于父类,重载无要求
- 异常处理:子类重写后,异常范围可以更小,但是不能抛出新异常;重载无关
抽象类
抽象类的概念和缘由
- 当我们对父类定义的时候,发现ta的很多子类方法各不相同,我们很难找一套统一的规范,去定义这个父类,这个时候要想起来借助抽象类
- abstract class声明的类
抽象类注意点
- 一个抽象类可以不含抽象方法,但是一个抽象方法必须在某个抽象类中
- 一个抽象类必须被子类继承,该子类则必须重写抽象类中的全部抽象方法
- 抽象类不能被实例化,不能使用new
- 抽象类不能使用final声明,因为final的类,不能有子类,而抽象类必须有子类
- 抽象方法,不带方法体(即大括号)
- 抽象类 中的方法可以是抽象方法,也可以不带abstract
- *抽象类能否有构造方法?*如果子类b,继承了某个抽象类a,则其实本质上a也在jvm内存中会实例化(与普通类继承一样的,子类new的时候,都是先调用父类的构造方法super,之后再创建子类自己的构造)。因此抽象类,可以有构造方法,编译器只是限制了coder。
抽象类和普通类的区别
- 抽象类必须用public或者protected修饰,否则不能让子类继承(所以不能用private修饰)。
- 抽象类默认的缺省为public,普通类为default。
- 抽象类不能被new实例化,但本质上在其子类new对象时候,也会调用抽象父类的构造方法(上述第7点)
- 如果一个子类b继承抽象类a,有两个选择,要么把b也定义成abstract;要么b必须实现a所有的抽象方法。
接口
接口来源
- 如果一个抽象类,所有的方法都是抽象方法,全部的属性都是全局常量(public static final修饰),那么此时可以将这个类定义成一个接口
- interface 接口名称{
全局常量;
抽象方法;
} - 接口编辑的时候,类与方法前面可以省略public,因为缺省值为public
接口的实现
接口可以多实现:
格式:
class 子类 implements 父接口1,父接口2…{
}
接口的继承
接口因为彼此都是抽象部分,不存在具体的实现,因此允许多继承,例如:
interface C extends A,B{
}
- 如果一个类可以实现接口,又要继承抽象类,可以通过以下的格式编写:
class 子类 extends 父类 implements 父接口1,父接口2...{
}
- 如果一个接口要想使用,必须依靠子类,子类(如果不是抽象类的话)要实现接口中的所有抽象方法
面向接口编程思想:
核心是定义(规范、约束)与实现的分离
优点:
- 降低程序的耦合性
- 有利于程序的拓展
- 有利于程序的维护
接口和抽象类的区别
- 抽象类要被子类继承,接口要被子类实现;
- 接口只能声明抽象方法,抽象类可以声明抽象方法,也可以写非抽象方法
- 接口里面定义的变量,必须是public static final常量,抽象类中的是普通变量
- 抽象类使用继承来使用,无法多继承。。接口使用实现来使用,可以多实现
- 抽象类中可以包含static方法,但接口中不允许(static静态方法不能被子类重写,因此接口中不能声明static方法)
- 接口不能有构造方法,但是抽象类可以有。
多态
多态的概念
- 当B、C两个类,继承了抽象类A,则B、C生成的对象b与c,就具有多种形态,即多态,一个类可能会存在多种形态(b,c)
- 方法的重写与重载,也是一种多态的表现
- 多态例子如下(父类引用,指向子类对象):
Person p=null;
Student s=new Student();
Teacher t=new Teacher();
p=s;//父类引用,指向子类对象
p.say();
p=t;//父类引用,指向子类对象
p.say();
即一个p,可以有学生student这种形态,也可以有teacher这种形态。
多态的使用
- 对象的类型转换:
- 向上转型:子类实例转换成父类,如上面的父类引用,指向子类对象
- 向下转型:父类实例转换成子类,此时需要进行类型的强制转换,如下
Person p1=null;
Student s=new Student();
Teacher t=new Teacher();
p1=s;//向上转型
p1.say();
p2=t;//向上转型
Student s2=(Student) p1;//向下转型,需要强制转换;
Student s3=(Student) p2;//向下转型,需要强制转换,但是p2本质上不是一个Student,此时代码会报错
- 多态在使用时,尤其是面向接口编程时,可以通过传入一个接口的实现,调用某个方法;而在方法定义的时候,仅仅需要定义入参为某个接口/抽象类,如下。
//定义下列方法实现
public static void say(Person p){
System.out.println("我是:"+p.name);
}
//用户可以进行不同实现的调用
public static void main(){
Student s=new Student();
say(s);
Nurse n=new Nurse();
say(n);
}
instanceof
- 格式:实例化对象 instanceof 类//此此操作返回boolean类型,检查该对象是否属于该类
Object类
- Object类是所有类的父类(基类),如果一个类没有明确地继承某个具体的,则其继承自Object类。
- 参照上述多态的概念,Object类可以接收任意的引用数据类型。
API
- 方法:鼠标hover+ctrl,点击进入某个类,查看这个类的详细源码
- 可以查看官方java的API文档
toString
- Object类里面定义了toString,这个类的目的是返回Object字符串描述,以充分地描述这个Object,便于人们阅读,官方建议所有的子类都重写这个方法
- 官方的toString()方法,默认返回一个字符串,该字符串由下列组成:类名(全称)+@+hash(十六进制)
- 通过重写toString,修改System.out.println()时,打印出的效果
- eclipse快捷键shilt+alt+s,调出下拉框后,选择toString,即可自动创建。
equals
- Object类里面定义了equals,默认是对比两个对象的内存地址是否相同,而不是对比两个对象中具体的内容
- 常用的符号,==就是比较两个对象的内存地址
-
可以通过重写equals方法,定制每个类的对比方法,比如我们判断用户是否是同一个,在equals里面判断其身份证号或者手机号 - 重写时候,注意返回值类型为boolean,入参为Object
public boolean equals(Object o){
if(this == o){
return ture;
}
if(o==null){
return false;
}
if(o instanceof Person){
Person p2=(Person) o;
if(this.name.equals(p2.name)&&(this.mobile ==p2.mobile)){
return ture;
}else{
return false;
}
}else{
return false;
}
}
- equals一般要根据业务要求进行对比,如果业务没有要求,一般要求类中每个属性都相同
- 用ctrl+alt+s,下拉表单进行快速创建默认的equals()方法
内部类
- 将一个类定义到另外一个类的内部或者方法里面,称为内部类
- 包括下面几种:
- 成员内部类
- 局部内部类(代码块中定义。。。方法中定义的内部类)
- 匿名内部类(匿名类是不能有名字的类,它们不能被引用,只能在创建时用 new 语句来声明它们,只能在编写时候,使用ta一次)
- 静态内部类(成员内部类中加上static修饰)
成员内部类
- 成员内部类是伴随着对象而存在的,对象创建了内部类才能去用
- 成员内部类使用的时候,说明对象已经创建,因此可以无条件使用外部类的成员属性和成员方法,包括private和static的
- 当成员内部类与外部类,有相同名字的属性和方法时,会发生隐藏现象,默认访问内部类的
- 下面是例子,注意有代码注释的地方
/*
* 测试成员内部类
*/
public class Outer {
private int x=999;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
class Inner{
private int x=888;
public void say() {
System.out.println(x);//输出999
System.out.println(Outer.this.x);//输出888
}
}
}
/*
* 测试类
*/
public class Test {
public static void main(String[] args) {
Outer o=new Outer();
//用这个方法,声明成员内部类,同时new一个成员内部类
Outer.Inner i=o.new Inner();
i.say();//输出888,999
}
}
局部内部类
- 定义在一个局部代码块中的内部类,TA和成员内部类的区别在于,其作用域仅限于方法内
- 局部内部类,如同方法中的局部变量一样,是不能有权限修饰符:public protected private以及static修饰符
- 局部内部类,example如下:
public class Demo1{
public static void main(String[] args){
//定义局部内部类
class Person{
public void say(){
System.out.Println("这是Person类,局部内部类");
}
}
//使用局部内部类
Person p=new Person(){
p.say();
}
}
}
- 局部内部类的使用场景:一般是在调用某个方法,但该方法入参包括某个对象(或接口),此时我们需要在代码块中,新new一个对象(或一个接口的实现implements),此时我们使用局部内部类,去实现这个接口,或者继承这个抽象类。
- 只能访问final型的局部变量
匿名内部类
- 匿名内部类,即这个类只需要使用一次,所以我们并没有给这个类命名…ta的声明形式也有点奇怪,见下:
new 父类构造器(参数列表)|实现接口(){
//匿名内部类的类体部分
}
,例子如下:
public interface Person {
public void say();
public void drink();
}
public class Test {
public static void main(String[] args)
//调用匿名内部类
Person pe=new Person() {
public void say() {
System.out.println("这是匿名内部类实现1");
}
public void drink() {
// TODO Auto-generated method stub
System.out.println("这是匿名内部类实现2");
}
};
haha(pe)
}
//写了一个方法,传入的需要是接口的实现
public static void haha(Person p) {
p.drink();//返回:这是匿名内部类实现2
}
}
- 使用匿名内部类,一定要继承一个类,或者实现一个接口,但是二者不能兼得,必须二选一;
- 匿名内部类中,不能定义构造函数
- 匿名内部类中,不能存在static方法和static成员变量
- 匿名内部类不能是抽象的
- 匿名内部类属于一种局部内部类,局部内部类的限制也同样适用
- 匿名内部类只能访问final型的局部变量,例子如下,上面的匿名内部类的例子也可以这样写
public class Test {
public static void main(String[] args)
int aa=100;
//调用匿名内部类
Person pe=new Person() {
public void say() {
System.out.println("这是匿名内部类实现1"+aa);
}
public void drink() {
// TODO Auto-generated method stub
System.out.println("这是匿名内部类实现2"+aa);
}
};
haha(pe);//这是匿名内部类实现2
class PersonImp implements Person {
@Override
public void say() {
// TODO Auto-generated method stub
System.out.println("这是匿名内部类实现1-1"+aa);
}
@Override
public void drink() {
// TODO Auto-generated method stub
System.out.println("这是匿名内部类实现2-2");
}
}
PersonImp pi=new PersonImp();
haha(pi);//这是匿名内部类实现2-2
//aa=9;//如果此行不注释,上述代码会无法编译,
//因为在java编译的时候,会把所有的class编译成一个单独的class文件,此时由于内部类内部使用了这个内部类外部定义的变量,
//因此jvm会将这个变量copy一份,保存下来,放到这个文件中。因此系统从规则上限制了这个变量不可更改。
//所以内部类使用的局部变量,jvm会将这个变量,默认前面加上final。(只不过代码中省略了。。。)
//注释掉后,就美问题了!
}
//写了一个方法,传入的需要是接口的实现
public static void haha(Person p) {
p.drink();//返回:这是匿名内部类实现2
}
}
静态内部类
- 静态内部类也是定义在另外一个类里面,只不过在类的前面加了一个statci关键字。
- 使用静态内部类new对象,不需要创建外部类的对象就可以,如下图例子
- 静态内部类不能使用外部类的,非static的成员和方法(因为外部类,可能压根还没new出新对象,而非static的成员或方法必须依赖实际的对象。因此只能使用static的)
/*
* 测试类
*/
public class Test {
public static void main(String[] args) {
Outer.InnerStatic os=new Outer.InnerStatic();
os.say();
}
/*
* 测试静态内部类
*/
public class Outer {
private int x=999;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
static class InnerStatic{
private int x=123;
public void say() {
System.out.println("这是静态内部类内部");
}
}
}
包装类
- java中8中基础数据类型,并不是引用数据类型,部分的接口/方法入参定义为Object的时候,不能传入基础数据类型,因此引入了包装类进行解决。
| 序号 | 基本数据类型 | 包装类 |
|---|---|---|
| 1 | int | Integer |
| 2 | char | Character |
| 3 | float | Float |
| 4 | double | Double |
| 5 | boolean | Boolean |
| 6 | byte | Byte |
| 7 | short | Short |
| 8 | long | Long |
- 包装类也提供了一些基础方法,用int i = Integer.parseInt(String s),可以将字符串转换成int,其他的double,long也有。
- 其中Character,Boolean,直接是Object的子类,其他的6类包装类都是Number的子类。
可变参数
- 有时候,定义的方法需要接收多个相同类型的参数,比如定义一个sum方法,这个方法可以接收任意个int,进行加总求和,此时就用可变参数
- 数据类型… 参数名称,例子如下:
/*
* 可变参数测试
*/
public class KebianParam {
public static void main(String[] args) {
// TODO Auto-generated method stub
sum("haha",20,12,13);
}
/*
* int... nums:表示可以传入0-n个int
* 在方法内部,可变参数可以用数组作为载体
*/
public static void sum(String s,int... nums) {
int res=0;
for(int i=0;i<nums.length;i++) {
res+=nums[i];
}
System.out.print(s+res);
}
}
- 注意,如果入参有多个,可变参数必须在参数列表的最后,否则语法上会报错
475

被折叠的 条评论
为什么被折叠?



