String | a | = | new String() ; |
---|---|---|---|
类 | 引用类型变量 | 对象(实例),new表示调用构造器 |
5.1 类和对象
5.1.1 类中各个内容的定义
//1.定义类:没有protected/private和static
public/default final/abstract class 类名{
初始化块;
0到多个构造器;
0到多个成员变量;
0到多个方法;
内部类
}
//2.定义成员变量:没有abstract
public/protected/private/default static final 类型 变量名[=默认值];
//3.定义方法:全
public/protected/private/default static final/abstract 返回值类型 方法名(形参列表){
}
//4.定义构造器:构造器名必须与类名相同,不可以有返回值类型(void也不行),如果有则变成普通方法。没有static和final/abstract
public/protected/private/default 构造器名(形参列表){
}
5.1.2 对象的产生和使用
Person p = new Person();
p.name;
5.1.3 对象,引用和指针
5.1.4 对象的this引用
this关键字就是调用该方法的对象的引用,即:谁在调用这个方法,this就指向谁
/*现象1:run()方法中调用jump()方法时,如果没有this关键字,那么必须在run()方法中创建一个新对象来调用jump()方法。
但由于调用this可表示调用run()方法的对象,又由于jump()与run()在同一类中,
所以该对象也有jump()方法,所以可以在run()方法中通过this.jump()调用jump()方法,
而不必重新生成一个对象。
且java允许对象的一个成员调用另一个成员时省略this,所以出现以下现象,实际上是省略this造成的。*/
run{
jump();
}
/*现象2:类方法中不可使用实例变量/实例方法。static修饰类中成员时,表示该成员变量属于类本身,而不属于该类的某个实例(对象),
由于static修饰与否将变量或方法分为实例变量(方法),和静态(类)变量(方法),而类变量(方法)的调用方式为类名.变量名(方法名),实例变量(方法)的调用方式为对象(实例).变量名(方法名),
由于类方法由类名直接调用(可以没有对象(实例)的存在),所以此时在类方法中的this无法表示调用该方法的对象,所以不可以用this调用该类中的实例变量(方法),自然也不可以省略this,所以在类方法中不可以直接使用实例变量(方法)*/
static eat(){
//如果sleep为非static方法,下面会编译报错
//sleep();
//如果一定想用sleep方法,可以先创建该类对象,再用该实例调用方法
Person p = new Person();
p.sleep();
}
5.2 方法详解
5.2.1 方法的所属性
static修饰:属于类
无static修饰:属于对象
5.2.2 方法的参数传递机制
java中只有值传递,无论是方法中传入的变量为基本类型还是引用类型,都相当于复制一个同样的变量,放到另一个栈区中,此时内存中同时存在main方法栈区和该方法栈区
- 基本类型的值传递:由于基本类型变量的值是存在于自身所在的栈区中,所以方法中的变量的值改变,方法外的变量的值不变
- 引用类型的值传递:由于引用类型的值(即对象)保存在它所指向的堆中的对象中,且方法外的变量与复制出的这个变量指向同一个对象,所以此时如果在方法中操纵该对象,改变其值,那么方法外的变量指向的对象的值也会变化(因为是同一个对象)
5.2.3 参数个数可变的方法:
//调用方便,个数可变的形参必须在参数列表最后
public void test(int a ,String ... books){
System.out.println(books[0]);
}
//等同于下面方法
public void test(int a ,String[] books){
}
5.2.4 递归方法:
5.2.5 方法重载:多重载入
- 方法名相同,形参列表不同
- 系统根据调用方法时传入的参数来判断具体使用几个同名方法中的哪个方法
- 返回值与修饰符可以相同可以不同,与重载没关系
- 子类与父类也可构成重载:
- 子类与父类中存在方法名相同,形参列表不同的两个方法,由于子类对象可以直接调用父类的方法,因此对于子类对象构成重载
- 对于父类对象,无法调用子类中方法,所以无法构成重载
5.3 成员变量和局部变量
- 几种变量间区别
- 实例变量与类变量
- 类变量:从类准备阶段开始存在,直到类被完全销毁
- 实例变量:从对象被创建开始存在,直到无任何引用变量引用该对象
- 成员变量与局部变量
- 成员变量:无需显示初始化,系统会在类准备阶段(类变量),或者创建该类对象时(实例变量),进行默认初始化。
- 局部变量:除形参外,必须显示初始化,即必须为局部变量赋值后才可以使用。且两者作用域不同
- java中成员变量与局部变量可以同名,局部变量会覆盖成员变量,此时可用this(实例变量),或类名(类变量)作为调用者来限定访问成员变量,这种是不可省略this的情况
- 实例变量与类变量
- 成员变量初始化与内存中运行机制
package com.wsh.object;
public class Person {
static String name;
String eyenum;
public static void main(String[] args) {
//第一次使用该类,在堆中会分配一块内存,主要存放Person类(类变量name等),name初值为null
String name = Person.name;
//会在栈中存放p这个变量(引用),在堆中分配一块内存空间,存放p指向的Person对象与其实例变量eyeNum等,eyeNum初值为null
Person p = new Person();
//会在栈中存放p1这个变量(引用),在堆中分配一块内存空间,存放p1指向的Person对象与其实例变量eyeNum等,eyeNum初值为null
Person p1 = new Person();
//修改Person类所在内存中的name值为2,如果此时调用p.name或p1.name值都会是2,因为name属性是所有对象共用的
Person.name = "2";
}
}
- 变量使用规则:
- 使用优先级:代码块局部变量>方法局部变量>成员变量,成员变量耗内存,扩大作用域
- 使用成员变量的情况:描述类/对象固有信息,描述类/对象运行状态,需在某个类中多个方法间共享的变量
5.4 隐藏和封装
5.4.1 理解封装
把对象的成员变量和实现细节隐藏起来,把方法暴露出来,通过访问控制符public,protected,private,default来实现
5.4.2 使用控制符控制作用域
- 作用域
范围\控制符 | private | default | protected | public |
---|---|---|---|---|
所在类的同一类中 | √ | √ | √ | √ |
所在类的同一包中 | √ | √ | √ | |
所在类的子类中 | √ | √ | ||
全局范围 | √ |
- 详细区分
- protected与default区别:protected多了可以被不同包下的子类中使用
- protected与public区别:public多了不同包下的非子类
- 修饰类中成员
//不可以修饰局部变量
访问控制符用于控制一个类的成员(成员变量,成员方法等)是否可以被其他类访问,对于局部变量,其本来就不可以被其他类访问,其作用域只是所在方法,因此不可以用访问控制符修饰
- 修饰外部类
//只可以用public和default修饰外部类,且同一java源文件只能有一个public类
//不可以用private修饰可以理解为如果一个类不想被其他任何类访问,那么他的存在没什么意义
//不可以使用protected修饰可以理解为当使用protected修饰类时,一定是希望这个类可以被与它不在同一包中的子类继承(default与protected的差异),又不想被不同包下其他类继承(protected与public差异),这本身就是矛盾的,因为只有继承之后,才知道是不是子类
public class A{
//B为内部类,可用任何访问控制符修饰
public class B{
}
}
- 访问控制符使用的基本原则
- 大部分成员变量使用private,一些static修饰的成员变量可以用public,有些方法只用于辅助实现该类的其他方法,应用private
- 如果某个类的主要功能是作为其他类的父类,即该类中大部分方法可能仅希望被其子类重写和使用,而不想被外界调用,那么应该用protected修饰该方法
- 构造器一般用public修饰,从而允许其他地方创建该类对象,外部类通常希望被其他类调用,通常使用public修饰
5.5 package,import和import static
注意:其他包中无法import default package中的类,也无法使用他们
5.5.1 package lee.wu.liu
此时对应的.class文件必须被放在lee\wu\liu下才有效
- javac -d . Hello.java:会自动建立package后对应的文件夹结构来存放相应的class文件,而javac Hello.java不会
- java lee.wu.liu.Hello 应在lee文件夹所在路径执行该命令,不应进入lee文件夹中执行
- 当虚拟机要加载lee.wu.liu.Hello时,依次寻找CLASSPATH环境变量所包含的路径,看这些路径下是否有lee\wu\liu\Hello.class而不是看Hello.class文件。同一包中的类不必位于同一目录下,即如果C,D两盘符下都有lee\wu\liu路径即可,此时只需让CLASSPATH包含这两个路径即可,虚拟机会将他们当做同一包下类处理
- 开发时应如下设计存放位置
5.5.2 import
使用不同包中其他类时,总需使用该类全名(包名+类名),为简化编程,使用import。注意import只是为了简化编程,不能将classpath中没有的类引入并使用,如果想使用classpath中没有的类需要使用URLClassLoader的loadClass方法加载class文件
- 导入单个类:import java.util.Arrays
- 导入一个包下所有类(只能导入包下的类,不能导入包下的包):import java.util.*
- 若两个包中有相同的类,那么仍需使用该类的全名进行调用
- java默认所有源文件导入java.lang包,所以使用String,System类时无需导包
5.5.3 import static
导入静态成员变量和方法
5.6 深入构造器
5.6.1 使用构造器进行初始化:
- 构造器最大的作用就是创建对象时进行初始化(构造器不能创建对象,即调用构造器前java会先为该对象分配内存)
- 程序员如果没有为java类编写任何构造器时,系统会自动为该类提供一个构造器,因此java类至少包含一个构造器
- 一旦程序员编写了自定义构造器,系统就不会再提供默认构造器,如果仍希望使用原默认的无参构造器,需要人为重载构造器
5.6.2 重载构造器:
package com.wsh.object;
public class Apple {
private String name;
private String color;
double weight;
public Apple(String name,String color){
this.name = name;
this.color = color;
}
public Apple(String name,String color,double weight){
//调用A中代码,相当于复制A中代码到B中,减少了代码的而重复,利于维护,但是必须放在第一行
this(name,color);
this.weight = weight;
}
}
5.7 类的继承
5.7.1 继承的特点
//extends修饰,extends是扩展的意思
//每个类只有一个直接父类,但可以实现多个接口。如果没有显示指定某类的直接父类,则默认继承java.lang.Object类
public class SubClass extends SuperClass{
}
5.7.2重写父类的方法:(重新)
子类中包含父类同名,同形参列表的方法的现象被称为重写
- 重写规则:两同,两小,一大。子类比父类,按方法定义的顺序:大、小、同、同、小
- 方法名同形参列表同
- 子类方法返回值类型小于等于父类的,子类方法声明抛出的异常类型小于等于父类的
- 子类方法的访问权限大于等于父类的
- 重写注意
- 子类方法和父类方法应同为实例方法
- 类方法被类方法重写不会生效
- 实例方法(类方法)被类方法(实例方法)重写时,编译就会报错
- 遵守两同,但不遵守两小一大时,编译就会报错
- 如果要在子类中调用父类中被覆盖的方法,可以使用super表示父类对象的引用(实例方法),或表示父类类名(类方法),从而进行调用
- 如果父类中方法为private修饰,则该方法对子类是隐藏的,因此无法对其重写,即使在子类中定义了一个与父类private方法同名,同形参列表的方法,也不是重写,只是重新定义了一个方法
5.7.3 super限定
package com.wsh.object;
public class BaseClass {
public String name;
public String age;
public BaseClass(String name){
this.name = name;
}
}
package com.wsh.object;
public class SubClass extends BaseClass{
//1.SubClass与BaseClass中都有name属性
public String name;
public SubClass(String name) {
//父类构造器一定是为父类中的成员变量值进行初始化,即这个name是父类的
super(name);
this.name = "吴思含";
}
public static void main(String[] args) {
//2.创建子类对象时,会在堆中分配四块内存空间,分别存放子类,父类,子类对象,父类对象
SubClass sub = new SubClass("刘雪婷");
sub.syso();
}
public void syso(){
//3.使用子类对象的name属性
System.out.println(name);
//4.通过super来使用父类对象的name属性
System.out.println(super.name);
//5.当某方法中出现变量(age)时,先查找本方法中是否有名为age的局部变量,再在当前类中查找,再在父类及父类的父类...中查找,如果都找不到编译报错,这就是所谓的子类对象可以获得父类对象的成员变量
System.out.println(age);
}
}
5.7.4 调用父类构造器
- 子类构造器执行前必须先调用父类构造器,如果没有显示调用,默认调用父类的无参构造器,所以执行子类构造器之前,一定会将其所有父类,每个类中至少一个构造器进行执行(如果父类构造器中第一行使用了this进行构造器重载,那么this调用的构造器也会被执行,那么该父类中相当于有两个构造器被执行),由于Object为所有类父类,所以创建对象时Object构造器一定会被执行
- 如果父类没有无参构造器那么会编译错误,此时可以在子类构造器第一行显式使用super(形参列表)来调用父类有参构造器,或为父类创建无参构造器,由于使用this进行构造器重载也必须放在第一行,所以this和super无法同时出现
5.8 多态
java引用变量有两种类型:编译时类型(由变量的类型决定,例:Person p),和运行类型(由实际引用的对象的类型决定,例:new Wusihan()),两种类型不一致时会产生多态
5.8.1 多态性
首先明确几个概念
- 引用调用实例变量时,引用是什么类型,调用的就是哪个类型中的实例变量
- 引用调用方法时,引用指向的对象是什么类型,调用的就是哪个类型中的方法
- 如果引用的类型,或其指向的对象的类型中,没有想调用的实例变量或方法,就找该类型的父类中的同名实例变量或方法
package com.wsh.object;
public class SubClass extends BaseClass {
public String name;
//子类独有
public void methodA(){
}
//父类被重写
@Override
public void methodB(){
}
}
package com.wsh.object;
public class BaseClass {
public String name;
public void methodB(){
}
}
BaseClass bc1 = new SubClass();
BaseClass bc2 = new BaseClass();
//bc1.methodA();编译报错
//运行时
//实际执行的为SubClass的方法
bc1.methodB();
//实际执行的为BaseClass的方法
bc2.methodB();
//即同样的引用变量,调用同样的方法名,实际上执行的是两个不同的方法,就是多态
//多态只是对方法而言,成员变量不存在多态,即以下两种调用的实际上都是BaseClass的成员变量的值
String a = bc1.name;
String b = bc2.name;
//但如果bc1有getName方法,获取到的就是SubClass的成员变量的值了
String c = bc1.getName();
5.8.2 引用变量的强制类型转换
//编译时类型为父类型,无法调用子类方法
//bc1.methodA();编译报错
//如果想调用,需强制转换为子类型
((SubClass)bc1).methodA();
//运行时类型为子类型,无法调用父类被重写的方法
//bc1的运行时类型为子类(SubClass),bc1是无法调用父类BaseClass的被重写的方法(methodB)的,如果非要调用父类被覆盖的方法,只能创建父类对象来调用
bc2.methodB();
- 强制类型转换时的注意事项:
- 基本类型中数值类型与布尔类型不能强制转换
- 引用类型的强制类型转换只能在具有继承关系的两个类型间进行转换,否则编译就报错
- 首先,强制类型转换指的是变量之间的转换,且由父类类型转为子类类型,即向下转型。自动转换指子类类型转父类类型(一定能转,且无需人为写转换)。只有指向子类对象的父类型引用才能被强制转为子类型,否则强转时报错,可以用instance of判断是否可以强转成功
BaseClass a = new SubClass();
//强制转换
SubClass b = (SubClass)a;
SubClass c = new SubClass();
//自动转换
BaseClass d = c;
- instanceof 运算符:引用变量 instanceof 类A
- 判断该引用变量所指向的对象是否为后面的类或其子类的一个实例,如果是返回true,不是返回false。
- null instanceof任何类,都返回false。
- 如果instanceof 返回true意味着该引用变量可以被强转为类A,且编译不会报错(前可以强转后)
- 引用变量的类型必须与类A具有父子关系,否则编译都会报错
5.9 继承与组合
继承是实现类复用的重要手段,即可以使父类反复使用,但继承会破坏父类的封装性,而使用组合的方式实现复用,可以提供更好的封装性
5.9.1 使用继承的注意点
每个类都应封装其内部信息(成员变量)和实现细节(工具方法),但在继承中由于父类有方法/成员变量由protected,default修饰,子类可以访问到这些成员变量/方法,而如果子类中访问这些成员变量/方法的方法修饰符为public,相当于扩大了父类方法/成员变量的作用域,破坏了封装
- 注意事项:
- 将父类中所有成员变量设置为private,不让子类直接访问父类的成员变量
- 父类中工具方法用private修饰,让子类无法访问,父类中需被外部类使用,又不希望子类重写,用public final修饰,父类中方法希望被子类重写,但不希望被其他类随意访问,应用protected修饰
- 尽量不要在父类构造器中调用将要被子类重写的方法,因为创建子类对象前会先调用父类的构造器,而如果父类的构造器中包含子类重写的方法,那么此时实际调用的是子类的方法,如果该方法中含有子类的实例变量(例如name),那么此时如果包含代码name.length();会报错,因为name还没有被成功初始化,类变量有初值的情况不会报错,因为类变量再类创建时就被初始化了
- 应使用继承的情况:
- 子类额外增加了属性,而不仅仅是改变属性的值
- 子类需增加新的方法或重写父类的方法
5.9.2 使用组合实现复用
package com.wsh.object;
public class Animal {
public void beat(){
System.out.println("心脏跳动");
}
public void breath(){
beat();
System.out.println("吸气吐气");
}
}
//使用继承实现复用,没再写一次breath()或beat()方法,可以理解为就是复用了
package com.wsh.object;
public class Bird extends Animal{
public static void main(String[] args) {
Bird b = new Bird();
b.beat();
}
}
//使用组合实现复用,相当于将Animal类型变量作为Bird成员变量,并在构造器中为a初始化,之后就可以直接用Bird对象来获取其内Animal类型的成员变量,并调用其方法实现复用
package com.wsh.object;
public class Bird{
private Animal a ;
public Bird(Animal a){
this.a = a;
}
public Animal getA() {
return a;
}
public void setA(Animal a) {
this.a = a;
}
public static void main(String[] args) {
Animal a = new Animal();
Bird b = new Bird(a);
b.getA().breath();
}
}
5.10 初始化块
- 初始化块与构造器功能相近,都是用来初始化对象的,初始化块可以用static修饰,表示为类的初始化块
- 初始化块与构造器:实际上初始化块是一个假象,使用javac命令编译后,该java类中初始化块会消失,被还原到每个构造器中,且位于构造器所有代码之前