Java面向对象要点

面向对象

一、Java内存分析

先分析一下Java编程中会用到的数据类型:

  • 基本数据类型;
  • 引用数据类型:包含数组、类class、接口interface。

从另一个维度,数据又会被分为:

  • 常量;
  • 变量。

同时,static修饰符也会影响数据的作用域和生命周期。

在这里插入图片描述

Java栈的区域很小,大概2MB左右,特点是存取的速度特别快。

栈有2个基本特性:

  1. 先进后出;
  2. 栈内存,通过“栈指针”来创建与释放空间。

2个基本特性分别造成的“结果”:

1.  栈只用于存储会随着方法调用结束而消亡的“局部变量”;
2.  存取速度特别快(仅次于PC寄存器),且栈存放的变量必须==长度是固定的==。

综合这两点,栈存放的是:

  • 基本数据类型的数据

  • 引用数据类型的引用

    例如:

    int a = 10;	// a=10 存储在栈中
    Person p = new Person();	// p=对象的地址 存储在栈中
    							// 真正的实例存储在堆中
    

堆存储的是:类的对象本身

存储方法:new关键字,告诉JVM,创建一个新的对象,开辟一块新的堆内存空间。

堆的特点:

  1. 区域大,灵活管理;
  2. 创建对象时不必确定需要多少存储空间;
  3. 堆内存的释放由GC(垃圾回收器)自动完成:当一个对象没有被任何引用变量指向后,GC会择机清除它。
方法区

存放的是

  • 类信息
  • 静态的变量
  • 常量
  • 成员方法
分类:

线程私有:栈区、PC寄存器;

各线程共享:堆区、方法区。

其他:
1. PC寄存器(隶属CPU)

PC寄存器保存的是:当前正在执行的 JVM指令的 地址 。
在Java程序中, 每个线程启动时, 都会创建一个PC寄存器。

2. 本地方法栈

保存本地(native)方法的地址。

先浅显的理解一下: native关键库在Java与C/C++联合编程时使用,调用C/C++直接写好的方法(DDL).

更深入

https://www.cnblogs.com/czwbig/p/11127124.html

疑惑:整型数组作为返回值到底是怎么传递的?

答:Java是一种值传递语言,数组(是一种引用类型)作为返回值时,返回的是数组引用的拷贝。假设接收到该拷贝的变量是p,则p指向原数组。

整个过程中,外层方法中的p先入栈,再进入方法,方法中的局部变量数组引用(假设为a)入栈,后伴随着方法的退出而出栈,p被赋上a的值。即真正的数组(在堆中,不随方法的退出而消亡)的首地址。

二、类与对象

  • 类的定义、对象的创建,属性的定义、创建、初始化,方法的定义、调用。

  • 构造方法重载:建议总是定义无参和全参的构造函数。

  • 匿名对象:只用一次的对象。

  • 封装:private, setter(限制数据范围,避免逻辑错误), getter。

  • this:

    • 调用类中的属性

    • 调用类中的方法或构造方法

      ​ 在一个构造方法中,调用另一个构造方法时,(如在无参内调用有参)必须将this语句放在第一行

    • 表示当前对象

  • static

        1. 静态成员在类加载时加载并初始化。
        2. 无论一个类存在多少个对象,静态的属性,永远在内存中只有一份,
        3. 在访问时: 静态不能访问非静态,非静态可以访问静态。
    
  • abstract

    abstract class AbstractClass{	// 抽象类
    	public abstract void abstractMethod();	// 抽象方法,没有方法体
    }
    

    抽象类必须用public或protected修饰(如果为private修饰,那么子类无法继承,也就无法实现其中的抽象方法),默认缺省为public.

    抽象类不能用final声明,因为final修饰 的类不能有子类。

    抽象类的子类必须实现其所有抽象方法,否则也要声明为abstract类。

  • 继承:只有单继承、多重继承,没有多继承。

  • 创建一个子类对象时,会先创建其父类对象,再创建子类对象。

  • super:指向实例化的父类对象。

    • 访问父类的属性
    • 调用父类的构造方法(未明写时,子类构造方法默认调用父类的无参构造函数),写在构造函数第一行
    • 调用父类的其他方法

    所谓的继承:子类拥有了一个指向父类的地址super。

  • 重写(override)

    参数列表和返回类型必须与被重写方法相同;

    访问权限不能比父类中被重写的方法的访问权限更低;

    声明为private或static的方法不能被重写,但是能够被再次声明

  • @Override注解:加在重写的方法上一行。当父类中并没有该方法时,会报错。(避免打错名字的低级错误~)

  • final

    • 修饰属性、变量:属性成为常量属性,必须在声明时赋值;变量成为常量,只能赋值1次。

      public static final 全局常量(常量属性在声明时就必须被赋值,往后不可再更改,则常量属性与对象无关,不如声明成static的,放进方法区常量池)

      注意常量命名规范

    • 修饰类:不能被继承

    • 修饰方法:不能被重写

三、接口

概念

如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。

定义格式:

interface 接口名称{
	全局常量 ;
	抽象方法 ;
}
1. 全局常量:
    	public static final String INFO = "内容";
	简写后:
        String INFO = "内容"2. 抽象方法:
        public abstract void print();
	简写后:
        void print();
面向接口编程思想

这种思想是:接口是定义(规范,约束)与实现的分离。(名实分离)

优点:

1. 降低程序的耦合性;
2. 易于程序的扩展;
3. 有利于程序的维护。
接口的实现
class 子类 extends 父类 implements 父接口1,父接口2...{
}
接口的继承

接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承

interface C extends A,B{
}  
接口v.s.抽象类
  1. 抽象类要被子类继承,接口要被类实现。
  2. 接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
  3. 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  4. 抽象类使用继承来使用,无法多继承。 接口使用实现来使用,可以多实现。
  5. 抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)。
  6. 接口不能有构造方法,但是抽象类可以有 。

四、多态

就是对象的多种体现形式。

对象的类型转换:
  • 向上转型:子变父 (父类引用指向子类对象,一定可以转)

    父类 父类对象 = 子类实例;

  • 向下转型:父变子 (该对象引用的实质是子类型才能转)

    子类 子类对象 = (子类)父类实例;

instanceof关键字

判断某个对象是否是指定类的实例:实例化对象 instanceof 类

Object类
  • Object的多态:它是所有类的基类,因此使用Object可以接受任意的引用数据类型。

  • toString方法:返回对象的字符串表示形式 ;

    Object的toString方法: 返回对象的内存地址。(建议重写)

  • equals方法:指示某个其他对象是否“等于”此对象 ;

    Object的equals方法:实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值x和y ,当且仅当x和y引用同一对象( x == y具有值true )时,此方法返回true 。 (建议重写)

    equals方法重写时的五个特性:
    自反性 :对于任何非空的参考值x,x.equals(x)应该返回true.
    对称性 :对于任何非空引用值x和y,x.equals(y)应该返回true当且仅当y.equals(x)返回true.
    传递性 :对于任何非空引用值x,y和z,如果x.equals(y)返回true且y.equals(z)返回true,则x.equals(z)应该返回true。
    一致性 :对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false ,前提是未修改对象上的equals比较中使用的信息。
    非空性 :对于任何非空的参考值x,x.equals(null)应该返回false。
    
    例:以String类型的number来判断是否相等:
       @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Express express = (Express) o;
            return Objects.equals(number, express.number);
        }
    
  • getClass()方法:获得实质类名。

五、内部类(不怎么用)

将一个类定义在另一个类里或一个方法里。

包含4种:

  1. 成员内部类

    成员内部类是最普通的内部类,它的定义为位于另一个类的内部

    成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
    不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,如果要访问外部类的同名成员,需要以下面的形式进行访问:

    外部类.this.成员变量
    外部类.this.成员方法

    class Outter { // 外部类
    	private double x = 0;
    
        public Outer(double x) {
    		this.x = x;
    	} 
        
        class Inner { // 成员内部类
    		public void say() {
    			System.out.println("x="+x);
    		}
    	}
    }
    
    
    public class Test {
    	public static void main(String[] args) {
        	// 外部使用成员内部类
            Outter outter = new Outter();
            Outter.Inner inner = outter.new Inner();
    	}
    } 
    
  2. 静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。

public class Test {
	public static void main(String[] args) {
	Outter.Inner inner = new Outter.Inner();
	}
} 

class Outter {
	public Outter() {
	} 
    
    static class Inner {
		public Inner() {
		}
	}
}
  1. 局部内部类

    局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

    注意:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

    class Person{
    	public Person() {
    	}
    } 
    
    class Man{
    	public Man(){
    	}
        
        public Person getPerson(){
    		class Student extends Person{ // 局部内部类
    			int age =0;
    		} 
            return new Student();
    	}
    }
    
  2. 匿名内部类

    使用匿名内部类,必须要继承一个父类或者实现一个接口。同时它没有class关键字,因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。

    匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:
    new 父类构造器(参数列表)|实现接口()
    {
    	// 匿名内部类的类体部分
    }
    

    在使用匿名内部类的过程中,我们需要注意如下几点:

    1. 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
    2. 匿名内部类中是不能定义构造函数的。
    3. 匿名内部类中不能存在任何的静态成员变量和静态方法。
    4. 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
    5. 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
    6. 只能访问final型的局部变量。(局部内部类也是;原因是编译器将局部/匿名内部类编译成了一个单独的.class文件,其所要访问的局部变量做了备份放在这个文件中;如果这个局部变量在运行时会发生改变,则这个局部/匿名内部类使用的它的值将会是改变前的,代码逻辑会出现问题)

六、包装类

在Java中有一个设计的原则“一切皆对象”,Java中为了使8种基本数据类型也满足这一点,引入了八种基本数据类型的包装类。

分为两大类型:

  • Number:Integer、Short、Long、Double、Float、Byte都是Number的子类,表示是一个数字。
  • Object:Character、Boolean都是Object的直接子类。

两个操作:

  • 装箱:将一个基本数据类型变为包装类。
  • 拆箱:将一个包装类变为一个基本数据类型。
装箱操作:
在JDK1.4之前 ,如果要想装箱,直接使用各个包装类的构造方法即可,例如:
	int temp = 10 ; // 基本数据类型
	Integer x = new Integer(temp) ; // 将基本数据类型变为包装类
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自减操作。例如:
	Float f = 10.3f ; // 自动装箱
	float x = f ; // 自动拆箱
	System.out.println(f * f) ; // 直接利用包装类完成
	System.out.println(x * x) ; // 直接利用包装类完成
字符串转换

使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。

在Integer类中提供了以下的操作方法:
public static int parseInt(String s) :将String变为int型数据
在Float类中提供了以下的操作方法:
public static float parseFloat(String s) :将String变为Float
在Boolean 类中提供了以下操作方法:
public static boolean parseBoolean(String s) :将String变为boolean
....
....

七、可变参数

返回值类型 方法名称(数据类型…参数名称){
	// 参数在方法内部,以数组的形式来接收
}

注意:可变参数只能出现在参数列表的最后。

八、代码块

  • 普通代码块
    在执行的流程中出现的代码块。

  • 构造代码块
    在类中的成员代码块。在每次对象创建时执行,执行在构造方法之前。

    可以用于 无论通过哪个构造方法构造对象,都希望执行的代码块。

  • 静态代码块
    在类中使用static修饰的成员代码块。在类加载时执行。每次程序启动到关闭,只会
    执行一次

    可以用于 加载唯一资源。

  • 同步代码块
    在后续多线程技术中学习。

    面试题:
    构造方法 与 构造代码块 以及 静态代码块 的执行顺序
    静态代码块 --> 构造代码块 --> 构造方法

九、单例设计模式

保证程序在内存中只有一个对象存在(被程序所共享)。

两种实现方式:

一、懒汉式:随着类的加载在内存中对象为null,当调用 getInstance 方法时才创建对象(延迟加载)
二、饿汉式:随着类的加载直接创建对象(推荐开发中使用)

实现步骤:

1.保证一个类只有一个实例,实现方式:构造方法私有化
2.必须要自己创建这个实例,实现方式:在本类中维护一个本类对象(私有,静态)
3.必须向整个程序提供这个实例,实现方式:对外提供公共的访问方式(getInstance方法,静态)

懒汉式实现如下:

class Single{
	private Single(){}
	private static Single s1 = null;
    
	public static Single getInstance(){
		if(s1 == null){
			s1 = new Single();
		} 
        return s1;
	}
} 

饿汉式实现如下:

class Single2{
	private Single2(){}
	private static Single2 s = new Single2();
	
    public static Single getInstance(){
		return s;
	} 
    
    void print(){
		System.out.println("Hello World!");
	}
}  

十、递归(很少用)

在方法的定义中使用方法自身。也就是说,递归算法是一种直接或者间接调用自身方法的算法 。

能使用循环解决的不要用递归哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值