3、java面向对象编程

时间:2021-05-30 19:55:12

1、面向对象内存分析

  • 栈的特点

    (1)JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)

    (2)栈属于线程私有,不能实现线程间的共享!

    (3)栈的存储特性是:先进后出,后进先出。由系统自动分配,速度快!是一个连续的内存空间。

    (4)栈描述的是方法执行的内存模型,每个方法被调用会创建一个栈帧(存储局部变量、操作数、方法出口等),方法结束,栈帧消失。

  • 堆的特点:

    (1)JVM只有一个堆,被所有的线程共享。

    (2)堆是一个不连续的内存空间,分配灵活,速度慢!

    (3)堆用于存储好的对象和数组(数组也是对象)

  • 方法区(又叫静态区):它在堆里。

    (1)JVM只有一个方法区,被所有的线程共享.

    (2)用于存放程序里唯一的内容(类对象,静态变量、字符串常量等)

  • 举例

    (1)代码

 public class SxtStu {

     //属性fields
int id;
String sname;
int age; Computer comp; //引用数据类型 //方法
void study(){
System.out.println("电脑名称是:"+comp.brand);
} void play(){
System.out.println("我在玩游戏!哈哈哈");
} //构造方法。用于创建这个类的对象。无参的构造方法可以由系统自动创建。
SxtStu(){
System.out.println("调用了无参的构造方法!");
} //程序执行的入口,必须要有
//javac Sxtstu.java , java Sxtstu
public static void main(String[] args) {
SxtStu stu = new SxtStu(); //创建一个对象
stu.id=1001;
stu.sname= "小明";
stu.age = 18; Computer c1 = new Computer();
c1.brand = "联想"; stu.comp = c1; stu.play();
stu.study(); }
} class Computer {
String brand;
}

    (2)画图说明程序运行中内存变化

3、java面向对象编程

2、构造器-->构造方法

  • 基本要点

    (1)通过new关键字调用

    (2)构造器虽然有返回值,但是不能定义返回值类型(返回值类型肯定是本类),不能在构造器里使用return关键字。

    (3)如果没有定义构造器,编译器会自动定义一个无参的构造函数。

    (4)构造器的方法名必须和类名一致。

  • 代码实例

public class BallGame {

    double x;
double y; // 重载构造方法
public BallGame(double x,double y ){
this.x=x;
this.y=y;
}
public void jiaGame(){
System.out.println(x+y);
} public static void main(String[] args) {
BallGame ballGame = new BallGame(12,30);
ballGame.jiaGame();
}
}

3、 this以及static关键字

  • 创建对象分为四步:

    (1)分配对象空间,并将对象成员变量初始化为0或者null

    (2)执行属性值的显示初始化--->赋值

    (3)执行构造方法

    (4)返回对象的地址给相关的变量

  • this关键字:本质是创建好的对象的地址!由于构造方法调用前,对象已经创建。因此构造方法可以用this代表当前对象。
  • this常用的用法:

    (1)变量产生歧义!在普通方法里,this指向调用该方法的对象。构造方法里,this指向初始化的对象。

    (2)用于重载构造方法,避免相同的初始化代码。但只能在构造方法里用,并且必须位于构造方法的第一句。

    (3)不能用于static修饰的方法里。

  • static关键字:用static声明的成员变量为静态成员变量,也称为类变量。

    (1)为类的共享变量,在类被载入时被显示初始化。

    (2)一般用类名.类属性/方法 来调用。

    (3)在static方法里不可直接访问非static的成员

4、垃圾回收机制

  • 内存管理

    (1)java的内存管理很大程度就是对象的管理,包括对象空间的分配和释放。

    (2)对象空间的分配:使用new关键字创建对象即可

    (3)对象空间的释放:将对象赋值null即可。垃圾回收器将负责回收所用“不可达”对象的内存空间

  • 垃圾回收过程

    (1)发现无用的对象---->没有变量引用该对象。

    (2)回收无用对象占用的内存空间。

  • 垃圾回收算法。

    (1)引用计数法

    (2)引用可达法

  • 分代垃圾回收机制-->不同生命周期的对象可以采取不同的回收算法,提高回收效率。对象有三种状态:年轻代,年老代,持久代。

    (1)年起代:新产生的对象首先放在Eden区。目标是尽可能快速的收集那些生命周期短的对象即Minor GC.每次Minor GC会清理年轻代的内存算法采用效率高的复制算法。但频繁的操作会浪费内存空间。单年轻代堆满对象后,将未失效的对象放入年老代区域。

    (2)年老代:在年轻代里经历了N(默认是15次)垃圾回收后仍然存活的对象,会被放入老年代里。当年老代对象很多的时候,会启动Major GC和Full GC(全量回收),来一次大扫除。全面清理年轻代和老年代的对象。但这样对系统内存压力大。

    (3)持久代:存放静态文件。

3、java面向对象编程

3、java面向对象编程

    (4)导致Full GC可能原因:

        1、年老代被写满

        2、持久代被写满

        3、System.gc()被显示调用(程序建议GC启动,不是调用GC)

        4、上一次GC之后Heap的个区域分配策略动态变化

  • 容易导致内存泄漏的操作

    (1)创建大量无用对象

    (2)静态集合类使用--->HashMap、Vector、List使用容易出现内存泄漏,这些静态变量的生命周期和应用程序一致。所有的Object不能被释放。

    (3)各种连接对象(IO流、数据库连接对象、网络连接对象)未关闭

    (4)释放对象时,没有删除相应的监听器。

     (5)要点:程序员无权调用垃圾回收器;可以使用System.gc().该方法只是通知JVM,并不是运行垃圾回收器。尽量少用,会启动Full GC;finalize方法是提供程序员释放对象的方法,但尽量少用。

5、package与import

  • 静态初始化块

    (1)如果希望加载后,对整个类进行某些初始化操作,可以使用static初始化块。类第一次载入时先执行static代码块并且只执行一次。

    (2)在类初始化时执行,不是在创建对象时执行。静态初始化块里不能访问非static成员。

    (3)执行顺序:先执行Object静态初始化块,再向下执行子类的静态初始化块。构造方法执行顺序也是这样。

  • package

(1)作用:
                        为了解决类之间的重名问题。
                        为了便于管理类:合适的类位于合适的包!
             (2)怎么用?
                        通常是类的第一句非注释性语句。
                        包名:域名倒着写即可,再加上模块名,并与内部管理类。
             (3)注意事项:
                       写项目时都要加包,不要使用默认包。
                       com.gao和com.gao.car,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。

    (4)JDK主要包:

      • java.lang:包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。
      • java.awt:包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
      • java.net:包含执行与网络相关的操作的类。
      • java.io: 包含能提供多种输入/输出功能的类。
      • java.util:包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。

  • import

    (1)为什么需要import?
        • 如果不适用import,我们如果用到其他包的类时,只能这么写:java.util.Date,代码量太大,不利于编写和维护。通过import可以导入其他包下面的类,从而可以在本类中直接通过类名来调用。
    (2)import怎么使用?
        • import java.util.Date;
        • import java.util.*; //导入该包下所有的类。会降低编译速度,但不会降低运行速度。
    (3)注意要点:
        •java会默认导入java.lang包下所有的类,因此这些类我们可以直接使用。
        • 如果导入两个同名的类,只能用包名+类名来显示调用相关类: java.util.Date date = new java.util.Date();
      (4)import static:用于导入指定类的静态属性, JDK5.0后增加!   

如何使用:
                             • import static java.lang.Math.*;//导入Math类的所有静态属性
                             • import static java.lang.Math.PI;//导入Math类的PI属性
        • 然后,我们可以在程序中直接使用:System.out.println(PI);

    (5)final关键字的作用

        • 修饰变量:一旦赋值,就不能重新赋值了。 final int A = 120;

        • 修饰方法:该方法不可被子类重写,但可以被重载。final void study(){}

        • 修饰类:不能被继承。final class A {}

6、参数传递机制

    java里,方法里所有参数都是“值传递”,也就是传递的是值的副本。不会改变原参数。

  • 基本数据类型传值:传递的是值的副本,副本改变不会影响原参数。
  • 引用类型参数传值:传递的值对象的内存地址。副本和园参数指向了同一个地址。改变副本指向对象的值,原参数的值也改变。

7、面向对象三大特性

  • 继承

    (1)类是对对象的抽象,继承是对某一批类的抽象,从而实现对现实世界更好的建模。提高代码的复用性!继承用extands关键字。子类是父类的扩展.不同的叫法:超类、父类、基类、子类、派生类

 public class TestExtends {
public static void main(String[] args) {
Mammal m1 = new Mammal();
m1.puru();
m1.eat();
}
}
class Animal {
String eyes=" 眼睛";
String name=" 无名";
public void eat(){
System.out.println(" 动物吃东西!");
}
}
class Mammal extends Animal {
// 哺乳
public void puru(){
System.out.println(" 小动物吃奶!");
}
}

    (2)子类继承父类的成员变量和成员方法,但不继承父类的构造方法.类加载时先加载父类的构造方法。

    (3) java中只有单继承, java中的多继承,可以通过接口来实现。

    (4) 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。

    (5)instanceof是二元运算符,左边是对象,右边是类;当对象是右边类或者子类所创建的对象时,返回true。反之返回false。

    (6)在子类中可以根据需要对从基类中继承来的方法进行重写。重写方法必须和被重写方法具有相同方法名称、参数列表和返回类型。重写方法的返回值类型小于等于被重写方法。

• 父类方法的重写:
                                    “==”:方法名、形参列表相同。
                                     “≤≤”:返回值类型和异常类型,子类小于等于父类。
                                     “≥”:访问权限,子类大于等于父类
                        • 构造方法调用顺序:
                                    根据super的说明,构造方法第一句 总是:super(…)来调用父类对应的构造方法。
                                    先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。

public class TestOverride {
public static void main(String[] args ) {
Animal animal = new Animal();
animal.shout();
Dog dog = new Dog();
dog.shout();
}
}
class Animal{
void shout(){
System. out.println("发出声音!");
}
}
class Dog extends Animal {
void shout(){
System. out.println("旺旺旺!");
}
}

    (7)对象的比较==和equals()

        • ==:
          • 比较两基本类型变量的值是否相等
          • 比较两个引用类型的值即内存地址是否相等,即是否指向同一对象。
        • equals() :
          • 两对象的内容是否一致
        • 示例
          • object1.equals(object2) 如:p1.equals(p2)
          • 比较所指对象的内容是否一样
          • 是比较两个对象,而非两个基本数据类型的变量
        • object1 == object2 如:p1==p2
          • 比较p1和p2的值即内存地址是否相等,即是否是指向同一对象。
        • 自定义类须重写equals(),否则其对象比较结果总是false。

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

  • 封装---->一般在javabeen里将属性和方法封装好。

    (1)隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。我们程序设计要追求“高内聚,低耦合”。高内聚 :就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合 :仅暴露少量的方法给外部使用。

    (2)封装要点:

      • 类的属性的处理:一般使用private. (除非本属性确定会让子类继承),提供相应的get/set方法来访问相关属性. 这些方法通常是public,从而提供对属性的读取操作。(注意:boolean变量的get方法是用:is开头!)
      • 一些只用于本类的辅助性方法可以用private, 希望其他类调用的方法用public

    (3)访问控制符:

      • 成员(成员变量或成员方法)访问权限共有四种:
        • public 公共的:可以被项目中所有的类访问。(项目可见性)
        • protected 受保护的:可以被这个类本身访问;同一个包中的所有其他的类访问;被它的子类(同一个包以及不同包中的子类)访问
        • default/friendly 默认的/友好的(包可见性):被这个类本身访问;被同一个包中的类访问。
        • private 私有的:只能被这个类本身访问。(类可见性)
      • 类的访问权限只有两种
        • public 公共的:可被同一项目中所有的类访问。 (必须与文件名同名)
        • default/friendly 默认的/友好的:可被同一个包中的类访问。

  • 多态

    (1)多态时方法的多态,不是属性的多态(多态和属性无关)

    (2)多态存在有3个必要条件:继承,方法重写,父类引用指向子类对象。

    (3)父类引用指向子类对象后,用该父类引用调用子类重写的方法时,就是多态了

package object;
public class TestPolym {
public static void main(String[] args) {
Animal animal = new Dog(); //向上可以自动转型
System.out.println(animal.age); //属性调用时,仍然是基类的属
性。属性没有多态!
// animal.shout();
animalCry(new Dog());
//传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
//如果没有多态,我们这里需要写很多重载的方法。如果增加一种动物,就需
要重载一种动物的喊叫方法。非常麻烦。
//有了多态,只需要增加这个类继承Animal基类就可以了。
animalCry(new Cat());
Dog dog = (Dog) animal; //编写程序时,如果想调用运行时类型的方
法,只能进行类型转换。不然通不过编译器的检查。
dog.gnawBone();
System.out.println(dog instanceof
Animal);
System.out.println(animal instanceof Cat);
System.out.println(animal instanceof Dog);
}
static void animalCry(Animal a){
a.shout();
}
}
class Animal {
int age=10;
public void shout(){
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
int age=28;
public void shout() {
System.out.println("旺旺旺!");
}
public void gnawBone(){
System.out.println(“我在啃骨头");
}
}
class Cat extends Animal {
int age=18;
public void shout() {
System.out.println("喵喵喵喵!");
}
}
  • 对象的转型

    (1)子类转换为父类:自动转换.即父类引用指向子类对象。

      • 上转型对象不能操作子类新增的成员变量和方法。
      • 上转型对象可以操作子类继承或重写的成员变量和方法
      • 如果子类重写了父类的某个方法,上转型对象调用该方法时,是调用的重写方法。
    (2)父类转换为子类:强制转换
      • (绝不是做手术,而是父类的真面目就是一个子类,否则会出现类型转换错误)

8、抽象类

    (1)是一种模版模式。抽象类为所有子类提供了一个通用模版,子类可以在这个模版基础上进行扩展。通过抽象类,可以避免子类设计的随意性。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
    (2)要点:

        • 抽象方法和抽象类均必须用abstract来修饰。
        • 抽象方法没有方法体,只需要声明不需实现。
        • 有抽象方法的类只能定义能抽象类
        • 相反抽象类里面的方法不一定全是抽象方法,也可能没有抽象方法。
        • 抽象类可以包含属性、方法、构造方法。
        • 抽象类不能实例化,及不能用new来实例化抽象类,只能用来被子类调用。
        • 抽象类只能用来继承。
        • 抽象方法必须被子类实现。抽象类的子类必须覆盖所有的抽象方法才能被实例化,否则还是抽象类
    (3)实例

 abstract class Animal {
abstract void shout(); //抽象方法没有方法体!
}
class Dog extends Animal {
void shout() { //必须重写父类的抽象方法否则编译通不过
// TODO Auto-generated method stub
System.out.println("旺旺旺!");
}
}

9、接口

    (1) 为什么需要接口?接口和抽象类的区别?

      • 接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。
      • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。如果你好人,则必须干掉坏人;如果你是
坏人,则必须欺负好人。
      • 接口的本质是契约,就像我们人间的法律一样。制定好后大家都遵守。
      • 项目的具体需求是多变的,我们必须以不变应万变才能从容开发,此处的“不变”就是“规范”。因此,我们开发项目往往都是面向接口编程!
    (2)接口相关规则

      • 接口中所有方法都是抽象的。
      • 即使没有显式的将接口中的成员用public标示,也是public访问类型的
      • 接口中变量默认用 public static final标示,所以接口中定义的变量就是全局静态常量。
      • 可以定义一个新接口,用extends去继承一个已有的接口
      • 可以定义一个类,用implements去实现一个接口中所有方法。
      • 可以定义一个抽象类,用implements去实现一个接口中部分方法

    (3)如何实现接口

      • 子类通过implements来实现接口中的规范
      • 接口不能创建实例,但是可用于声明引用变量类型。
      • 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
      • Java的类只支持单继承,接口支持多继承

      • Java中,一个类只能继承一个类,但同时可以实现多个接口,既可以实现多重继承的效果和功能,也避免的多重继承的危险性。如:class Student extents Person implements Runner,Flyer

10、内部类

    (1)定义:将一个类定义置入另一个类定义中就叫作“内部类”。

      • 内部类作为外部类的成员,可以直接访问外部类的成员(包括private成员),反之则不行。
      • 内部类做为外部类成员,可声明为private、默认、protected或public。
      • 内部类成员只有在内部类的范围之内是有效的。
      • 用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
      • 编译后生成两个类: OuterClass.class 和OuterClass$InnerClass.class

    (2)内部类分类:成员内部类 ;静态内部类 ;方法内部类 ;匿名内部类

 class Outer{
int outer_i = 100;
void test(){
Inner in = new Inner();
in.display();
System.out.println(in.a);
}
class Inner{
int a=5;
void display(){
System.out.println("display: outer_i = " + outer_i);
}
}
}

11、String类

  • 基础:

    (1)String类又称不可变序列。

    (2)String位于java.lang里,java默认导入java.lang下所有类。

    (3)Java字符串就是Unicode字符序列。例如字符串“Java”就是四个Unicode字符‘J’,‘a’,'v','a'组成的。

    (4)每个用双引号括起来的字符串就是String类的一个实例

  • 常量池

    (1)全局字符串常量池:其存放的内容是在类加载完成后存到String Pool里的。每个JVM只有一份,存放的是字符串常量的引用值(在堆里生成字符串对象实例)

    (2)class文件常量池:在编译的时候每个class都有的.在编译阶段,存放的是常量(文本字符串、final常量)和符号引用。

    (3)运行时常量:在类加载完成后,每个class常量池里的符号引用值转存到运行时常量池。