面向对象
Java六大存储区域
首先来了解一下Java的六大存储区域
-
寄存器
-
堆栈:存放基本类型的变量与数据、对象的引用;但对象本身不存放在栈中,创建程序时,Java编译器必须知道存储在堆栈内的所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。栈的大小越大可分配的线程数就越少。
-
堆:开辟内存的速度比堆栈慢得多。存放所有new出来的对象。不必知道大小和生命周期,需要在运行时分配内存。
-
静态存储:又叫方法区,存放如class、static变量
-
常量存储:final修饰、String(其实也是final修饰),但final static 为常量存储区
-
非Ram存储:磁带、磁盘
public class Sample { int s1 = 0; Sample mSample1 = new Sample(); public void method() { int s2 = 1; Sample mSample2 = new Sample(); } } Sample mSample3 = new Sample();
Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。
mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,而它自己存在于栈中。
结论:
局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储于堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。
面向对象简介
三大特性
-
封装性
- 将对象的属性和行为看成一个整体,“封装”成一个实体,即对象。
- 另一个含义是指“信息的隐蔽”,有些对象属性,只允许外界读取,不允许更改;亦可指功能的实现细节
-
继承性
父类的属性和行为,在子类中完成可以使用,子类也可以自定义自己的属性和行为。Java只允许单集成,通过interface弥补这个缺陷
-
多态性
- 方法的重载
- 对象多态:子类对象与父类对象进行相互转换,同一个行为,根据子类的不同,实现的功能也不相同(前提是子类有复写该方法)
类与对象的简单理解
类是对象的模板,而对象是类的实例
引用传递
引用传递是整个Java的精髓所在,而引用传递的核心概念也只有一个:一块堆内存空间(保存对象的属性信息)可以同时被多个栈内存所共同指向,则每一个栈内存都可以修改同一块堆内存空间的属性值。
封装
为什么要封装
隐藏实体的内部细节,不想被外部直接操纵,也符合现实世界的做法。(如存在银行中的存款(银行当成一个实体),不可能由存款人直接随意的修改金额,而是应该由银行内部去操作)
构造方法
定义
- 构造方法的名称和类名称保持一致
- 构造方法没有返回值
- 对象实例化一定需要构造方法,如果类中没有声明构造方法,则会自动生成一个无参数、无方法内容的构造方法。如果类中已经声明了一个构造方法,则不会生成默认的构造方法。即,类中至少存在一个构造函数。
类中定义属性的默认值,只有在构造执行完毕后(不是构造方法)才可以真正赋值。
构造方法实际上严格来讲是属于整个对象构造过程的最后一步,对象的构造需要为其分配空间,之后设置默认值(指的是类型的默认值),最后留给构造方法进行其他操作。所以可以定义的构造方法是对象实例化的最后一步,而其他的几步用户根本就看不见,也无法操作:
public class Temp {
public static void main(String[] args){
Person person = new Person();
}
}
class Person{
private String name = "tom";// 此属性只有在构造完成后,才能赋值
public Person(){
System.out.print(name);
}
}
匿名对象
一句话概括:没有栈内存指向的堆内存空间,就是匿名对象。并且只能使用一次,之后就被GC回收。
public class Temp {
public static void main(String[] args){
new Person().say();// 匿名对象
}
}
class Person{
private String name = "tom";// 此属性只有在构造完成后,才能赋值
public Person(){
System.out.print(name);
}
public void say(){
System.out.print("my name is "+name);
}
}
数组
常见的数据结构,牵扯到内存分配的问题。
public class Temp {
public static void main(String[] args) {
int[] arrs = {9, 6, 3, 2, 5, 7, 8, 4, 0, 1};
bubbleSort(arrs);
printArrs(arrs);
}
private static void printArrs(int[] arrs) {
for (int i = 0;i < arrs.length;i++){
System.out.print(arrs[i]+" , ");
}
System.out.println();
}
public static void bubbleSort(int[] arrs) {
for (int i = 0; i < arrs.length - 1; i++) {// 比较的趟数
for (int j = 0;j < arrs.length -1 - i;j++){// 每趟比较的次数
if (arrs[j] > arrs[j+1]) {
int temp = arrs[j];
arrs[j] = arrs[j+1];
arrs[j+1] = temp;
}
}
}
}
}
对象数组内存图
String
每一个定义的字符串都表示一个String对象,是String类的一个匿名对象。
两种实例化String对象的区别
public class Temp {
public static void main(String[] args) {
// 因为每个字符串是一个匿名的String对象,所以,以new创建的String对象,在堆中,会开辟两块空间,其中一块成为垃圾,被GC回收,真正指向的是以new创建的内存
String str1 = new String("hello");//不入池
String str2 = "hello";// 入池
String str3 = "hello";// 使用池里存在的字符串
System.out.println(str1 == str2);// false
System.out.println(str1 == str3);// false
System.out.println(str2 == str3);// true
}
}
public class Temp {
public static void main(String[] args) {
// 因为每个字符串是一个匿名的String对象,所以,以new创建的String对象,在堆中,会开辟两块空间,其中一块成为垃圾,被GC回收,真正指向的是以new创建的内存
String str1 = new String("hello").intern();//入池
String str2 = "hello";// 使用池里存在的字符串
String str3 = "hello";// 使用池里存在的字符串
System.out.println(str1 == str2);// true
System.out.println(str1 == str3);// true
System.out.println(str2 == str3);// true
}
}
字符串的内容一旦声明,则不可改变
public class Temp {
public static void main(String[] args) {
// 每一次字符串的拼接操作,都会产生垃圾,字符串本身的内容没有任何改变。改变的只是对象实例的指向
String str = "hello ";// 使用池里存在的字符串
str += "world";
str += "!!!";
System.out.print(str);// hello world!!!
}
}
this
public class Temp {
public static void main(String[] args) {
Person person = new Person("tim",11);
System.out.println(person);// Person{name='null', age=0}
}
}
class Person{
private String name;
private int age;
public Person(String name,int age){
name = name;// 这样赋值是无效的,因为name为局部变量
age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Temp {
public static void main(String[] args) {
Person person = new Person("tim",11);
System.out.println(person);// Person{name='tim', age=11}
}
}
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;// 这样赋值是无效的,因为name为局部变量,用this指明为全局变量
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 使用this调用构造方法的操作,一定要放在构造方法的首行。
- 如果一个类中存在了多个构造方法,并且这些构造方法都是用了this()相互调用,那么至少要保留一个构造方法没有调用其他构造,以作程序的出口。
static关键字
- static定义的方法不能调用非static的方法或者属性
- 非static定义的方法可以调用static的属性或方法
代码块
普通代码块
public class Temp {
public static void main(String[] args){
{
int x = 100;
System.out.println(x);//100
}
int x = 10;
System.out.println(x);//10
}
}
构造块
public class Temp {
public static void main(String[] args){
new Person();
new Person();
new Person();
}
}
class Person{
{
System.out.println("构造块");
}
public Person(){
System.out.println("构造方法");
}
}
执行结果:
构造块
构造方法
构造块
构造方法
构造块
构造方法
可以定义不同构造函数的共性代码,构造块在成员变量显示初始化以后执行
构造方法执行顺序:
- super();
- 成员变量显示初始化
- 构造代码块初始化
- 构造函数自定义内容初始化
静态块
public class Temp {
public static void main(String[] args){
new Student();
new Student();
new Student();
}
}
class Person{
public Person(){
System.out.println("父类构造方法");
}
}
class Student extends Person{
static{
System.out.println("静态块");
}
{
System.out.println("构造块");
}
public Student(){
System.out.println("构造方法");
}
}
执行结果:
静态块
父类构造方法
构造块
构造方法
父类构造方法
构造块
构造方法
父类构造方法
构造块
构造方法
随着类的加载而执行,仅执行一次;在静态变量显示初始化以后执行
内部类
当描述事物时,事物的内部还有事物,这个内部的事物还在访问外部事物中的内容。这时就将这个事物通过内部类来描述。
访问方式:
内部类可以直接访问外部类的所有成员,包含私有成员;而外部类想要访问内部类中的成员,必须创建内部类对象。
内部类被修饰为public不多见,因为更多的时候内部类已经被封装到了外部类中,不直接对外提供。
非静态内部类中,不允许定义静态成员,仅允许在非静态内部类中定义静态常量static final,如果想要在内部类中定义静态成员,内部类必须也要被修饰为static。
内部类最大的优点:可以方便地访问外部类的私有操作,或者是由外部类方便地访问内部类的私有操作(那是因为内部类其实持有了外部类的引用:外部类.this,对于静态内部类不持外部类.this,而是直接使用外部类名):
public class Temp {
public static void main(String[] args){
new Outter().outterPrint();
}
}
class Outter{
private final String outString = "Outter";
class Inner{
private final String innerString = "inner";
private void innerPrint(){
System.out.println(Outter.this.outString);
}
}
public void outterPrint(){
Inner inner = new Inner();
inner.innerPrint();
System.out.println(inner.innerString);
}
}
输出:
Outter
inner
内部类生成的class文件:Outter$Inner.class
实例化内部类对象
public class Temp {
public static void main(String[] args){
new Outter().new Inner().innerPrint();
}
}
class Outter{
private final String outString = "Outter";
class Inner{
private final String innerString = "inner";
public void innerPrint(){
System.out.println(Outter.this.outString);
}
}
public void outterPrint(){
Inner inner = new Inner();
inner.innerPrint();
System.out.println(inner.innerString);
}
}
之所以实例化外部类对象,主要因为内部类需要访问外部类中的普通属性,那么普通属性只有实例化后才可以使用。
如果内部类不希望被其他类所使用,那么也可以使用private关键字修饰,使其成为私有内部类:
public class Temp {
public static void main(String[] args){
// 编译器会报错
new Outter().new Inner().innerPrint();
}
}
class Outter{
private final String outString = "Outter";
private class Inner{
private final String innerString = "inner";
public void innerPrint(){
System.out.println(Outter.this.outString);
}
}
public void outterPrint(){
Inner inner = new Inner();
inner.innerPrint();
System.out.println(inner.innerString);
}
}
static定义内部类
相当于外部类,并且只能访问外部类中static的成员
当静态方法访问内部类时,内部类必须是静态的
实例化方式:
外部类.内部类 内部类对象 = new 外部类.内部类();
方法中定义内部类(又称局部内部类)
public class Temp {
public static void main(String[] args){
new Outter().method();
}
}
class Outter{
private String outString = "outString";
public void method(){
class Inner{
private void method(){
System.out.println(outString);
}
}
new Inner().method();
}
}
方法中的内部类如果要访问方法的参数或者方法中的变量,这些参数后者变量前一定要加final关键字修饰,否则无法使用(因为编译生成的class中,直接操作那个最终数值了(生命周期))
public class Temp {
public static void main(String[] args){
new Outter().method("hello");
}
}
class Outter{
private String outString = "outString";
public void method(final String str){
class Inner{
private void method(){
System.out.println(outString);
System.out.println(str);
}
}
new Inner().method();
}
}
继承
指的是扩充一个类已有的功能。
格式:
[类的修饰符] class 子类 extends 父类{}
子类又称为派生类,父类又称作超类。
public class Temp {
public static void main(String[] args){
Student student = new Student();
student.setAge(18);
student.setName("tom");
System.out.println(student.toString());
}
}
class Person{
private String name;
private int 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
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Student extends Person {
}
public class Temp {
public static void main(String[] args){
Student student = new Student();
student.setAge(18);
student.setName("tom");
System.out.println(student.toString());
System.out.println(student.getSchool());
}
}
class Person{
private String name;
private int 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
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* 另外定义父类中没有的方法
*/
class Student extends Person {
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
子类可以继承父类中的全部内容,但是对于私有操作属于隐式继承,而非私有操作属于显示继承。
继承的限制
-
只能多层继承,不允许多重继承。
-
父类中的私有也被继承下来,但却无法直接使用。
-
在继承中,如果要实例化子类对象,会默认先调用父类构造,为父类中的属性初始化,之后再调用子类构造,为子类中的属性初始化,即默认情况下,子类会找到父类中的无参构造函数。
public class Temp {
public static void main(String[] args){
new Son();
}
}
class Father{
public Father(){
System.out.println(“父类构造”);
}
}
class Son extends Father{
public Son(){
// 第一行默认调用super();
System.out.println(“子类构造”);
}
}
原因:子类会继承父类中的内容(显示初始化),所以子类在初始化时,必须先到父类中去执行父类的初始化操作。才可以更方便的使用父类中的内容。
在默认情况下,子类调用的是父类中的无参构造方法,而如果这个时候父类中没有无参构造,则子类必须通过super()显示调用指定参数的构造方法。
this和super调用构造方法时,不能够同时出现,两者是二选一的关系。
覆写
方法的覆写
构造方法不能被继承,因此不能覆写,但可以被重载。
当子类定义了和父类在方法名称、返回值类型、参数类型及个数完全相同的方法时,称为方法的覆写。
而当一个类中的方法被覆写后,如果实例化的是这个子类对象,则调用的方法就是覆写过的方法。
被子类所覆写的方法不能拥有比父类更严格的访问控制权限。(所以如果父类的方法为private,子类是无法覆写的,也不会这样去编码)
静态覆盖静态
当一个子类覆写了一个父类方法时,子类要想调用父类的被覆写过的方法,要在子类方法内加上super.
- this.方法():先从本类查找是否存在指定的方法,如果没有找到,则调用父类操纵;
- super.方法():直接由子类调用父类之中的制定方法,不再找子类。
属性的覆写
子父类中定义了一模一样的成员变量,都存在于子类对象中
public class Temp {
public static void main(String[] args){
new Son();
}
}
class Father{
public String msg = "father";// 此处暂时没有封装,严格来讲不符合开发标准,这种操作几乎没有意义,属性一定要封装,封装以后,就没有覆盖这一概念了
public Father(){
System.out.println("父类构造");
}
}
class Son extends Father{
public int msg =10;
public Son(){
System.out.println("子类构造");
System.out.println("msg = "+this.msg);
System.out.println("msg = "+super.msg);
}
}
构造方法不能被继承,因此不能被重写,但可以被重载
final关键字
- 使用final定义的类不能有子类,即无法被其他类所继承
- 使用final定义的方法不能被重写
- 使用final定义的变量,为常量,必须在定义时候就指定默认值,并且无法修改
多例设计模式(可用枚举代替)
public class Temp {
public static void main(String[] args) {
System.out.println(Sex.getInstance("male").getStr());
}
}
class Sex {
private static final Sex MALE = new Sex("male");
private static final Sex FEMALE = new Sex("female");
private String str;
private Sex(String sex) {
this.str = sex;
}
public static Sex getInstance(String sex) {
switch (sex) {
case "male":
return MALE;
case "female":
return FEMALE;
default:
return null;
}
}
public String getStr(){
return this.str;
}
}
多态
-
方法的多态性
- 重写(父类与子类之间,多态性的表现)
- 重载(一个类内,多态性的表现)
-
对象的多态性
- 向上转型:自动完成
- 向下转型:依赖于向上转型,需强制
- 对象的多态性和方法重写是紧密联系在一起的
public class Temp {
public static void main(String[] args) {
Father father = new Son();// 向上转型
father.print();// 因为子类重写了父类的方法,所以调用的是子类的print()
}
}
class Father{
public void print(){
System.out.println("Father print()");
}
}
class Son extends Father{
@Override
public void print(){// 重写方法
System.out.println("Son print()");
}
}
public class Temp {
public static void main(String[] args) {
Father father = new Father();// 向上转型
Son son = (Son) father;//ClassCastException 因为向下转型必须依赖于向上转型
father.print();// 因为子类重写了父类的方法,所以调用的是子类的print()
}
}
class Father{
public void print(){
System.out.println("Father print()");
}
}
class Son extends Father{
@Override
public void print(){// 重写方法
System.out.println("Son print()");
}
}
instanceof
判断某个对象是否为某个类的实例
public class Temp {
public static void main(String[] args) {
Father father = new Son();// 向上转型
System.out.println("father instanceof Father --- "+(father instanceof Father));
System.out.println("father instanceof Son --- "+(father instanceof Son));
father.print();// 因为子类重写了父类的方法,所以调用的是子类的print()
// father.extendMethod();// 父类没有此方法,调用不了
Son son = (Son) father;// 向下转型
if (son instanceof Son) {
son.extendMethod();
}
// 结果:
// father instanceof Father --- true
// father instanceof Son --- true
// Son print()
// Son extendMethod()
}
}
class Father{
public void print(){
System.out.println("Father print()");
}
}
class Son extends Father{
@Override
public void print(){// 重写方法
System.out.println("Son print()");
}
public void extendMethod(){// 子类拓展的方法
System.out.println("Son extendMethod()");
}
}
抽象类
最大特点:包含了抽象方法(只声明而未实现方法体的方法,定义时用abstract关键字完成,一定存在于抽象类中),用abstract声明。
- 抽象类不能直接实例化。必须有子类,使用extends继承
- 子类如果不是抽象类,必须重写父抽象类的全部抽象方法
- 抽象类不能用final定义,因为必须有子类
- 抽象类可以有构造方法,因为除了抽象方法之外,其还包括普通方法和属性,而属性一定要在构造方法执行完毕后,才可以进行初始化操作。
- 抽象类中可以不包含抽象方法,但是如果有抽象方法,则一定为抽象类。
- 抽象类能否用static定义?
-
如果直接使用static,肯定无法定义一个抽象类(abstract的类不能生产对象,但是static是属于类,而类已经是一个存在的对象(类其实也是一个对象,他是在class文件加载到虚拟机以后就会产生的对象,通常来说它是单例的,就是整个虚拟机中只有一个这样的类对象),这两个关键字在这上面有一个关键的矛盾点);
-
在内部类定义时,可以用static,表示定义了一个静态的内部抽象类
public class Temp { public static void main(String[] args) { Father.Son impl = new Impl(); impl.print(); } } class Father{ static abstract class Son extends Father{ public abstract void print(); } } class Impl extends Father.Son{ @Override public void print() { System.out.println("Impl print()"); } }
-
public class Temp {
public static void main(String[] args) {
Demo demo = new DemoImpl(33);
}
}
abstract class Demo{
public Demo(){
this.print();
}
abstract void print();
}
class DemoImpl extends Demo{
private int x = 100;
/**
* 一个类只有执行了构造方法后,才可以为类中的属性进行初始化操作,而在属性没有初始化之前,类中的所有属性,都是其对应数据类型的默认值
* @param x
*/
public DemoImpl(int x){
this.x = x;
}
@Override
void print() {
System.out.println(x);
}
}
上例中,实例化的是子类对象,根据对象的实例化流程,子类对象实例化前会首先调用父类构造,为父类中的属性初始化,但是这个时候子类对象由于没有被实例化,所以在DemoImpl类中的x的值为默认值0。父类中的构造又调用了print(),而此时实例化的是子类对象,所以调用的是子类重写的方法,结果为“x=0”
接口
定义时,全部由抽象方法和全局常量组成。使用interface关键字定义:
interface InterfaceName{
public static final ConstantType CONSTANT_NAME = "CONSTANT_NAME";
public abstract MethodType method(MethodParmType...parmName);
}
一个类可以同时实现多个接口,但是只能集成一个父类(或抽象类):
public class Temp {
public static void main(String[] args){
Son son = new Son();
System.out.println(son.NUMBER);
System.out.println(son.CONSTANT_NAME);
System.out.println("-------------------");
son.method("hello");
son.print();
// 输出结果:
// 5
// CONSTANT_NAME
// -------------------
// hello
// 5 , CONSTANT_NAME
}
}
interface A{
public static final String CONSTANT_NAME = "CONSTANT_NAME";
public abstract void method(String str);
}
/**
* 简化形式定义
*/
interface B{
int NUMBER = 5;
void print();
}
class Son implements A,B{
@Override
public void method(String str) {
System.out.println(str);
}
@Override
public void print() {
System.out.print(NUMBER+" , "+CONSTANT_NAME);
}
}
在Java中,每一个抽象类都可以实现多个接口,一个接口却不能继承抽象类,也不能继承一个父类,但是一个接口却可以同时继承多个接口,以实现接口的多继承操作。
接口与抽象类的区别
public class Temp {
public static void main(String[] args){
Son son = new Son();
Object obj = son;
C c = (C) obj;
System.out.print(c);
// 输出:C toString
}
}
interface A{
public static final String CONSTANT_NAME = "CONSTANT_NAME";
public abstract void method(String str);
}
/**
* 简化形式定义
*/
interface B{
int NUMBER = 5;
void print();
}
abstract class C{
@Override
public String toString() {
return "C toString";
}
}
class Son extends C{
}
包装类
public class Temp {
public static void main(String[] args){
Integer integer = new Integer(33);// 装箱
int num = integer.intValue();// 拆箱
System.out.println(num);
}
}
public class Temp {
public static void main(String[] args){
Integer integer = 33;// 自动装箱
int num = integer;// 自动拆箱
System.out.println(num);
}
}
对象池
public class Temp {
/**
* 与String类似,有池的概念
* @param args
*/
public static void main(String[] args){
Integer integer = new Integer(33); //不入池
Integer integer1 = 10;// 入池
Integer integer2 = 10;// 复用池里的对象
System.out.println(integer == integer1);// false
System.out.println(integer == integer2);// false
System.out.println(integer1 == integer2);// true
System.out.println(integer.equals(integer1));// false
}
}