3、java面向对象编程
1、面向对象内存分析
-
栈的特点
(1)JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
(2)栈属于线程私有,不能实现线程间的共享!
(3)栈的存储特性是:先进后出,后进先出。由系统自动分配,速度快!是一个连续的内存空间。
(4)栈描述的是方法执行的内存模型,每个方法被调用会创建一个栈帧(存储局部变量、操作数、方法出口等),方法结束,栈帧消失。
-
堆的特点:
(1)JVM只有一个堆,被所有的线程共享。
(2)堆是一个不连续的内存空间,分配灵活,速度慢!
(3)堆用于存储好的对象和数组(数组也是对象)
-
方法区(又叫静态区):它在堆里。
(1)JVM只有一个方法区,被所有的线程共享.
(2)用于存放程序里唯一的内容(类对象,静态变量、字符串常量等)
-
举例
(1)代码
1 public class SxtStu { 2 3 //属性fields 4 int id; 5 String sname; 6 int age; 7 8 Computer comp; //引用数据类型 9 10 //方法 11 void study(){ 12 System.out.println("电脑名称是:"+comp.brand); 13 } 14 15 void play(){ 16 System.out.println("我在玩游戏!哈哈哈"); 17 } 18 19 //构造方法。用于创建这个类的对象。无参的构造方法可以由系统自动创建。 20 SxtStu(){ 21 System.out.println("调用了无参的构造方法!"); 22 } 23 24 //程序执行的入口,必须要有 25 //javac Sxtstu.java , java Sxtstu 26 public static void main(String[] args) { 27 SxtStu stu = new SxtStu(); //创建一个对象 28 stu.id=1001; 29 stu.sname= "小明"; 30 stu.age = 18; 31 32 Computer c1 = new Computer(); 33 c1.brand = "联想"; 34 35 stu.comp = c1; 36 37 stu.play(); 38 stu.study(); 39 40 } 41 } 42 43 class Computer { 44 String brand; 45 }
(2)画图说明程序运行中内存变化
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)持久代:存放静态文件。
(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关键字。子类是父类的扩展.不同的叫法:超类、父类、基类、子类、派生类
1 public class TestExtends { 2 public static void main(String[] args) { 3 Mammal m1 = new Mammal(); 4 m1.puru(); 5 m1.eat(); 6 } 7 } 8 class Animal { 9 String eyes=" 眼睛"; 10 String name=" 无名"; 11 public void eat(){ 12 System.out.println(" 动物吃东西!"); 13 } 14 } 15 class Mammal extends Animal { 16 // 哺乳 17 public void puru(){ 18 System.out.println(" 小动物吃奶!"); 19 } 20 }
(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)实例
1 abstract class Animal { 2 abstract void shout(); //抽象方法没有方法体! 3 } 4 class Dog extends Animal { 5 void shout() { //必须重写父类的抽象方法否则编译通不过 6 // TODO Auto-generated method stub 7 System.out.println("旺旺旺!"); 8 } 9 }
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)内部类分类:成员内部类 ;静态内部类 ;方法内部类 ;匿名内部类
1 class Outer{ 2 int outer_i = 100; 3 void test(){ 4 Inner in = new Inner(); 5 in.display(); 6 System.out.println(in.a); 7 } 8 class Inner{ 9 int a=5; 10 void display(){ 11 System.out.println("display: outer_i = " + outer_i); 12 } 13 } 14 }
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常量池里的符号引用值转存到运行时常量池。