JAVA面向对象(基础)

JAVA面向对象基础

简单的面向对象类的定义与实现:

public class Student {
    public static void main(String[] args){
        Student stu = new Student(); // 创建对象
        stu.name = "www";  // 设置属性值
        stu.age = 19;    
        Computer c1 = new Computer();  // 创建一个电脑对象
        c1.brand = "联想";  // 将c1对象的属性brand设置为"联想"
        stu.cmp = c1;   // 将Computer类的对象引用赋值stu对象的cmp属性
        stu.study();    // 调用study方法
    }
    String name; // 姓名属性
    int age;  // 年龄属性
    Computer cmp;  // 创建一个电脑类的对象引用电脑,引用默认值为null
    Student(){   // 初始化方法
    }

    void study(){      // 方法
        System.out.println("我正在使用电脑:" + cmp.brand);
    }

}

class Computer{    // 定义电脑类
    String brand;    // 商标属性
}

面向对象的内存分析:

栈的特点如下:

1. 栈描述的是方法执行的内存模型.每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
2. JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
3. 栈属于线程私有,不能实现线程间的共享!
4. 栈的存储特性是“先进后出,后进先出”
5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!

堆的特点如下:

1. 堆用于存储创建好的对象和数组(数组也是对象)
2. JVM只有一个堆,被所有线程共享
3. 堆是一个不连续的内存空间,分配灵活,速度慢!

方法区(又叫静态区)特点如下:

1. JVM只有一个方法区,被所有线程共享!
2. 方法区实际也是堆,只是用于存储类、常量相关的信息!
3. 用来存放程序中永远是不变或唯一的内容.(类信息【Class对象】、静态变量、字符串常量等)

构造方法:

构造器也叫构造方法(constructor),用于对象的初始化.构造器是一个创建对象时被自动调用的特殊方法,目的是对象的初始化.
构造器的名称应与类的名称一致.Java通过new关键字来调用构造器,从而返回该类的实例,是一种特殊的方法

声明格式: 
[修饰符] 类名(形参列表){
    //n条语句
}

1.通过new关键字调用!!
2. 构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值.
3. 如果我们没有定义构造器,则编译器会自动定义一个无参的构造函数.如果已定义则编译器不会自动添加!
4. 构造器的方法名必须和类名一致!

例子:
class Point{
    double x;
    double y;
    public Point(double _x, double _y){  // 构造方法
        x = _x;                             
        y = _y;             
    }
    public double getDistance(Point p){    // 获取两点间的距离
        return Math.sqrt((p.x-x)*(p.x-x)+(p.y-y)*(p.y-y));
    }
}

public class Construt {
    public static void main(String[] args){
        Point p1 = new Point(0,0);    // new的时候会调用构造函数
        Point p2 = new Point(3,4);
        double distance = p2.getDistance(p1); // 调用对象的方法
        System.out.println(distance);
    }
}

构造方法重载(创建不同用户对象):

构造方法也是方法,只不过有特殊的作用而已.与普通方法一样,构造方法也可以重载.
例子:
public class User{
    String name;  // 姓名
    int id;   // 编号
    int iPhone;  // 手机号

    public User(){

    }

    public User(int id, String name){
        this.id = id;
        this.name = name;
    }

    public User(int id, String name, int iPhone){
        this.id = id;   // this代表这个对象
        this.iPhone = iPhone;
        this.name = name;
    }

    public static void main(String[] args) {
        // 创建三个用不同构造函数初始化的对象
        User u1 = new User();
        User u2 = new User(10000, "张三");
        User u3 = new User(10001, "李四", 12312300);
    }
}

雷区:
	如果方法构造中形参名与属性名相同时,需要使用this关键字区分属性与形参.如上例所示:
	this.id 表示属性id;id表示形参id

垃圾回收原理和算法:

·内存管理
Java的内存管理很大程度指的就是对象的管理,其中包括对象空间的分配和释放.
对象空间的分配:使用new关键字创建对象即可
对象空间的释放:将对象赋值null即可.垃圾回收器将负责回收所有”不可达”对象的内存空间.

·垃圾回收过程
	任何一种垃圾回收算法一般要做两件基本事情:
	1. 发现无用的对象
    2. 回收无用对象占用的内存空间

垃圾回收机制保证可以将“无用的对象”进行回收.无用的对象指的就是没有任何变量引用该对象.Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理.

·垃圾回收相关算法

1. 引用计数法
	堆中每个对象都有一个引用计数.被引用一次,计数加1. 被引用变量值变为null,则计数减1,直到计数为0,则表示变成无用对象.
优点是算法简单,缺点是“循环引用的无用对象”无法别识别.

【示例】循环引用示例  
public class Student {
    String name;
    Student friend;
 
    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student();
     
        s1.friend = s2;
        s2.friend = s1;        
        s1 = null;
        s2 = null;
    }
}

s1和s2互相引用对方,导致他们引用计数不为0,但是实际已经无用,但无法被识别.

2. 引用可达法(根搜索算法)
	程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,
	当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点.

通用的分代垃圾回收机制:

	分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率.
我们将对象分为三种状态:年轻代、年老代、持久代.JVM将堆内存划分为 Eden、Survivor 和 Tenured/Old 空间.
1. 年轻代
  所有新生成的对象首先都是放在Eden区. 年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,
   每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间.当“年轻代”区域存放满对象后,就将对象存放到年老代区域.
2. 年老代
	在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象,就会被放到年老代中.因此,可以认为年老代中存放的都是一些生命周期较长的对象.
	年老代对象越来越多,我们就需要启动Major GC和Full GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域.

3. 持久代
用于存放静态文件,如Java类、方法等.持久代对垃圾回收没有显著影响.
·Minor GC:
	用于清理年轻代区域.Eden区满了就会触发一次Minor GC.清理无用对象,将有用对象复制到“Survivor1”、“Survivor2”区中
(这两个区,大小空间也相同,同一时刻Survivor1和Survivor2只有一个在用,一个为空)

·Major GC:
	用于清理老年代区域.

·Full GC:
	 用于清理年轻代、年老代区域. 成本较高,会对系统性能产生影响.

垃圾回收过程:
    1、新创建的对象.绝大多数都会存储在Eden中,
    2、当Eden满了(达到一定比例)不能创建新对象,则触发垃圾回收(GC),将无用对象清理掉,
      然后剩余对象复制到某个Survivor中,如S1,同时清空Eden区
    3、当Eden区再次满了,会将S1中的不能清空的对象存到另外一个Survivor中,如S2,
      同时将Eden区中的不能清空的对象,也复制到S1中,保证Eden和S1,均被清空.
    4、重复多次(默认15次)Survivor中没有被清理的对象,则会复制到老年代Old(Tenured)区中,
    5、当Old区满了,则会触发一个一次完整地垃圾回收(FullGC),之前新生代的垃圾回收称为(minorGC)

JVM调优和Full GC

在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节.有如下原因可能导致Full GC:
1.年老代(Tenured)被写满
2.持久代(Perm)被写满
3.System.gc()被显式调用(程序建议GC启动,不是调用GC)
4.上一次GC之后Heap的各域分配策略动态变化

开发中容易造成内存泄露的操作:

建议:
  在实际开发中,经常会造成系统的崩溃.如下这些操作我们应该注意这些使用场景. 

  如下四种情况时最容易造成内存泄露的场景,请大家开发时一定注意:

  · 创建大量无用对象

  比如,我们在需要大量拼接字符串时,使用了String而不是StringBuilder.
    String str = "";
    for (int i = 0; i < 10000; i++) {   
        str += i;     //相当于产生了10000个String对象
    }

  · 静态集合类的使用
  像HashMap、Vector、List等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放.

  · 各种连接对象(IO流对象、数据库连接对象、网络连接对象)未关闭
  IO流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网络连接,不使用的时候一定要关闭.

  · 监听器的使用
  释放对象时,没有删除相应的监听器.

要点:
  1. 程序员无权调用垃圾回收器.
  2. 程序员可以调用System.gc(),该方法只是通知JVM,并不是运行垃圾回收器.尽量少用,会申请启动Full GC,成本高,影响系统性能.
  3. finalize方法,是Java提供给程序员用来释放对象或资源的方法,但是尽量少用.

this关键字:

· 对象创建的过程和this的本质

构造方法是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回该类的对象,但这个对象并不是完全由构造器负责创建.
创建一个对象分为如下四步:

1. 分配对象空间,并将对象成员变量初始化为0或空
2. 执行属性值的显示初始化
3. 执行构造方法
4. 返回对象的地址给相关的变量

this的本质就是“创建好的对象的地址”! 由于在构造方法调用前,对象已经创建.因此,在构造方法中也可以使用this代表“当前对象”.

this最常的用法:
1. 在程序中产生二义性之处,应使用this来指明当前对象;普通方法中,this总是指向调用该方法的对象.构造方法中,this总是指向正要
   初始化的对象.
2. 使用this关键字调用重载的构造方法,避免相同的初始化代码.但只能在构造方法中用,并且必须位于构造方法的第一句.
3. this不能用于static方法中.


示例----this()调用重载构造方法:

public class TestThis {
    int a, b, c;
 
    TestThis() {
        System.out.println("正要初始化一个Hello对象");
    }
    TestThis(int a, int b) {
        // TestThis();   // 这样是无法调用构造方法的!
        this(); // 调用无参的构造方法,并且必须位于第一行!
        a = a;// 这里都是指的局部变量而不是成员变量
    // 这样就区分了成员变量和局部变量. 这种情况占了this使用情况大多数!
        this.a = a;
        this.b = b;
    }
    TestThis(int a, int b, int c) {
        this(a, b); // 调用带参的构造方法,并且必须位于第一行!
        this.c = c;
    }
 
    void sing() {
    }
    void eat() {
        this.sing(); // 调用本类中的sing();
        System.out.println("你妈妈喊你回家吃饭!");
    }
 
    public static void main(String[] args) {
        TestThis hi = new TestThis(2, 3);
        hi.eat();
    }
}

static 关键字:

在类中,用static声明的成员变量为静态成员变量,也称为类变量. 类变量的生命周期和类相同,在整个应用程序执行期间都有效.
它有如下特点:
1. 为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化.
2. 对于该类的所有对象来说,static成员变量只有一份.被该类的所有对象共享!!
3. 一般用“类名.类属性/方法”来调用.(也可以通过对象引用或类名(不需要实例化)访问静态成员.)
4. 在static方法中不可直接访问非static的成员.

核心要点:
        static修饰的成员变量和方法,从属于类.
        普通变量和方法从属于对象的.

静态初始化块:

构造方法用于对象的初始化!静态初始化块,用于类的初始化操作!在静态初始化块中不能直接访问非static成员.
1. 上溯到Object类,先执行Object的静态初始化块,再向下执行子类的静态初始化块,直到我们的类的静态初始化块为止.
2. 构造方法执行顺序和上面顺序一样!!

参数传值机制:

Java中,方法中所有参数都是“值传递”,也就是“传递的是值的副本”. 也就是说,我们得到的是“原参数的复印件,而不是原件”.
因此,复印件改变不会影响原件.

· 基本数据类型参数的传值
    传递的是值的副本. 副本改变不会影响原件
· 引用类型参数的传值
    传递的是值的副本.但是引用类型指的是“对象的地址”.因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,
    也意味着原参数指向对象的值也发生了改变”.

package:

我们通过package实现对类的管理,package的使用有两个要点:

1. 通常是类的第一句非注释性语句.   如 com.oracle.test;
2. 包名:域名倒着写即可,再加上模块名,便于内部管理类. 如: package com.oracle.test;

注意:
   写项目时都要加包,不要使用默认包.
   com.gao和com.gao.car,这两个包没有包含关系,是两个完全独立的包.只是逻辑上看起来后者是前者的一部分.
   
                    【表 JDK中的主要包】
 Java中的常用包                               说明
 java.lang        包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能.
 java.awt         包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI).
 java.net         包含执行与网络相关的操作的类.
 java.io          包含能提供多种输入/输出功能的类.
 java.util        包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数.

导入类import:

如果我们要使用其他包的类,需要使用import导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名.
import后,便于编写代码,提高可维护性.

1. Java会默认导入java.lang包下所有的类,因此这些类我们可以直接使用.
2. 如果导入两个同名的类,只能用包名+类名来显示调用相关类:
        java.util.Date date  = new  java.util.Date();
3. import java.utils.*; 导入该包下所有的类.会降低编译速度,但不会降低运行速度.

4. 静态导入(static import)是在JDK1.5新增加的功能,其作用是用于导入指定类的静态属性,这样我们可以直接使用静态属性.
   import static java.lang.Math.*;  导入Math类的所有静态属性

继承:

子类的对象也是父类的对象,但反过来不成立.

java中子类必须调用父类的构造函数.如果没有用super()明确指明的话,默认调用没有参数的那个构造函数,如果自己定义了一个构造函数将
默认的无参构造函数覆盖掉,并且没有再定义无参构造函数,则会报错.
```可以通过super(参数)的形式在子类的构造函数中调用父类的有参数构造函数,此时,如果父类没有无参数构造函数也不会报错.

extends关键字用来表明父类:
    class Person{}
    class Student extends Person{}

    Student拥有Person类的属性和方法.

1. Java中类没有多继承,接口有多继承. 
2. 父类也称作超类,基类等.
3. Java中只有单继承,没有像C++那样的多继承.多继承会引起混乱,使得继承链过于复杂,系统难于维护.
4. 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法).
5. 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object.
6. Objdect是所有类的顶级父类.

instanceof是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true;否则,返回false.
如: st1 instanceof Student;

方法的重写:

子类通过重写父类的方法,可以用自身的行为替换父类的行为.方法的重写是实现多态的必要条件.
方法的重写需要符合下面的三个要点:
    1.“==”: 方法名、形参列表相同.
    2.“≤”:返回值类型和声明异常类型,子类小于等于父类. 
        如: public Person t1(){return new Person();} 
            public Student t1(){return new Student();}
    3.“≥”: 访问权限,子类大于等于父类.

toString方法:

  Object类中定义有public String toString()方法,其返回值是 String 类型.Object类中toString方法的源码为:
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

  根据如上源码得知,默认会返回“类名+@+16进制的hashcode”.在打印输出或者用字符串连接对象时,会自动调用该对象的toString()方法.
  打印对象时,会调用对象的toString方法,将toString的返回值作为打印值,可以重新子类的toString方法自定义打印内容.

==和equals方法:

“==”代表比较双方是否相同.如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象.
Object类中定义有:public boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑.
比如,我们在公安系统中认为id相同的人就是同一个人、学籍系统中认为学号相同的人就是同一个人.

Object 的 equals 方法默认就是比较两个对象的hashcode,是同一个对象的引用时返回 true 否则返回 false.
但是,我们可以根据我们自己的要求重写equals方法. 

JDK提供的一些类,如String、Date、包装类等,重写了Object的equals方法,调用这些类的equals方法, x.equals (y) ,
当x和y所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象),返回 true 否则返回 false.

super关键字:

super是直接父类对象的引用.可以通过super来访问父类中被子类覆盖的方法或属性.

使用super调用普通方法,语句没有位置限制,可以在子类中随便调用.

若是构造方法的第一行代码没有显式的调用super(...)或者this(...);那么Java默认都会调用super(),
含义是调用父类的无参数构造方法.这里的super()可以省略.

可以通过super.成员函数或变量来调用父类中被子类覆盖的方法或属性.

继承树追溯:

·属性/方法查找顺序:(比如:查找变量h)

  1. 查找当前类中有没有属性h
  2. 依次上溯每个父类,查看每个父类中是否有h,直到Object
  3. 如果没找到,则出现编译错误.
  4. 上面步骤,只要找到h变量,则这个过程终止.

·构造方法调用顺序:

   构造方法第一句总是:super(…)来调用父类对应的构造方法.所以,流程就是:先向上追溯到Object,然后再依次向下执行类的
   初始化块和构造方法,直到当前子类为止.

 注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复.

编程中封装的具体优点:

 1. 提高代码的安全性.
 2. 提高代码的复用性.
 3. “高内聚”:封装细节,便于修改内部代码,提高可维护性.
 4. “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作.

封装的实现—使用访问控制符:

Java中4种“访问控制符”分别为private、default、protected、public,它们说明了面向对象的封装性,
所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性.

                            【表 访问权限修饰符】    
    ===============================================================
    修饰符       同一个类      同一个包       不同包的子类        所有类 
    private       *                                                
    default       *            *
    protect       *            *               *
    public        *            *               *               *
    ===============================================================
  1. private 表示私有,只有自己类能访问
  2. default表示没有修饰符修饰,只有同一个包的类能访问
  3. protected表示可以被同一个包的类以及其他包中的子类访问
  4. public表示可以被该项目的所有包中的所有类访问

封装的使用细节:

  1. 一般使用private访问权限.
  2.  提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作
      (注意:boolean变量的get方法是is开头!).
  3. 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰.

多态:

多态指的是同一个方法调用,由于对象不同可能会有不同的行为.

多态的要点:
  1. 多态是方法的多态,不是属性的多态(多态与属性无关).
  2. 多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象.
  3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了.

对于子类和父类对象的相互转化,向上自动专型,向下需要强制类型转换.
向下指的是从父类==>子类.
由于子类继承了父类,所以子类拥有父类的方法和属性,所以向下可以实现自动转型.

多态代码实现:

public class Test{
    public static void main(String[] args) {
        // 分别创建类的实例对象
        Animal a1 = new Animal();
        animalSp(a1);    // 将其分别作为形参传入
        Dog d1 = new Dog();
        animalSp(d1);
        Cat c1 = new Cat();
        animalSp(c1);
    }
    
    // 多态的实现
    static void animalSp(Animal an){
        an.shout();
    }
}


class Animal{
    String name;
    public void shout(){  
        System.out.println("jiao le yi xia...");
    }
}

class Cat extends Animal{
    public void shout(){      // 重写shout方法
        System.out.println("miao miao miao");
    }
}

class Dog extends Animal{
    public void shout(){    // 重写shout方法
        System.out.println("wang wang wang");
    }
}

对象的转型(casting):

父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换.

向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法.
这时,我们就需要进行类型的强制转换,我们称之为向下转型!

在向下转型过程中,必须将引用变量转成真实的子类类型(运行时类型)否则会出现类型转换异常
ClassCastException.
如:
    Animal c1 = new Cat();
    Dog d1 = (Dog)c1;

final关键字:

1. 修饰变量: 被他修饰的变量不可改变.一旦赋了初值,就不能被重新赋值.
 final int MAX_SPEED = 120;

2. 修饰方法:该方法不可被子类重写.但是可以被重载!
 final void study(){}

3. 修饰类: 修饰的类不能被继承.比如:Math、String等.
 final class  A {}

猜你喜欢

转载自blog.csdn.net/qq_46456049/article/details/108586339