对象和类
类和对象
类的成员
属性
- 属性(成员变量)vs 局部变量
- 在类中声明的位置不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码器内、代码块内、构造器形参、构造器内部的变量 - 权限修饰符的不同
可以在声明属性时,指明其权限,使用权限修饰符(private、public、缺省、protected)
局部变量不可以使用权限修饰符 - 默认初始化值
属性根据其类型有默认初始化值
局部变量没有默认初始化值,因此在调用局部变量时必须赋值。 - 内存中加载位置不一样
属性加载到堆空间中(非static)
局部变量加载到栈空间
- 属性赋值的先后顺序
默认初始化->显示初始化/在代码块中赋值->构造器中赋值->通过对象.方法或对象.属性的方式赋值
由父及子 静态先行
方法
-
方法的声明: 权限修饰符 返回值类型 方法名(形参列表){
} -
方法的使用中,可以调用当前类的属性和方法。
方法中不能定义方法。
方法重载
- 在同一个类中,允许存在一个以上的同名方法,只要它们参数的个数或者参数的类型不同即可。
- 方法的签名:方法名和参数类型。因此不能有两个名字相同、参数类型也相同却有不同返回类型的方法。
- 判断是否重载和方法的权限修饰符、返回值类型、参数变量名、方法体都没有关系。
方法参数
方法参数的值传递机制
- 形参和实参
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传给形参的数据
- Java 是按值调用的,也就是方法得到的是所有参数值的一个副本
变量赋值:
- 基本数据类型:赋值的是变量所保存的数据值
- 引用数据类型:赋值的是变量所保存数据的地址值
因此对应的值传递机制:
- 参数是基本数据类型:实参赋给形参的是实参真实存储的数据值
- 参数是引用数据类型:实参赋给形参的是实参存储数据的地址值
可变个数的形参
- 格式:数据类型… 变量名
- 传入参数可以是0个,1个,2个
- 和形参类型也相同的数组之间不共存,即有String[] strs 就不能有String … strs,因为后者就是用来代替前者的,想更简化
- 可变个数形参在方法的形参中,必须声明在末尾,并且最多只能声明一个可变形参
构造器
- 构造器的作用:
创建对象,给对象初始化,因此构造器总是结合new运算符来调用 - 构造器与类同名
- 一旦显示调用了构造器,系统就不再提供默认的空参构造器
- 每个类中至少有一个构造器,多个构造器之间构成重载
代码块(初始化块)
- 代码块结构:
//非静态代码块
{
}
//静态代码块
static{
}
- 作用
用来初始化类、对象 - 代码块只能用static修饰,分为静态代码块和非静态代码块
- 静态代码块
随着类的加载而执行,且只执行一次。
作用:初始化类的属性 - 非静态代码块
随着对象的创建而执行 ,每创建一个对象就执行一次非静态代码块。
作用:可以在创建对象时,对对象的属性等进行初始化
- 静态代码块
内部类
-
Java中允许将类A声明在另一个类B中,则类A是内部类,类B是外部类
-
内部类分为成员内部类(静态、非静态)和局部内部类(方法内、代码块内、构造器内)
-
内部类可以被static 修饰,可以被private、protected、private、缺省四种成员修饰
-
创建成员内部类
-
内部类调用外部类的结构
-
局部内部类
对象
匿名对象
- 我们创建的对象,没有显示的赋给一个变量名,即为匿名对象
new Student().方法;//不显示赋给变量名直接调用方法
- 特征:匿名对象智能调用一次,因为new一次就是一个新的对象了
- 匿名对象的使用:作为形参
对象的内存解析
类的三大特征
封装
封装性体现:
- 将类的属性私有化(private),同时提供公共的(public)方法来获取(getXxx)和设置(setXxx)
- 不对外暴露的私有的方法
权限修饰符
- 封装性的体现需要权限修饰符来配合,Java规定四种权限(从小到大排列):private、缺省、protected、public
- 四种权限可以用来修饰类及类的内部结构:
属性、方法、构造器、内部类 - 修饰类只能用:缺省或public
- protected主要就是对不同包的子类放开了权限,相当于信任子类
继承
判断子类是否能继承父类的技巧 看是不是is A的关系
如 A is a B , A是student B是person就符合 而 A是cat就不符合
类、超类和子类
- 定义子类
继承的格式:
class A extends B{
}
A:子类(subclass)、派生类(derived class)、孩子类(child class)
B:超类(superclass)、基类(base class)、父类(parent class)
体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的结构:属性、方法。父类中声明为private的属性或方法,子类也获取了,只是因为封装性的影响不能直接调用而已。
方法重写
①子类继承父类以后,可以对父类中同名参数的方法,进行覆盖操作。
为了避免重写d的时候你以为你在重写,但你其实写了一个和父类完全无关的方法。所以在开发时我们可以用@Override标记要覆盖父类方法的那些子类方法:
@Override public boolean equals(Object other)
②重写的规定:
-
方法名和形参
子类重写的方法的方法名和形参列表 与 父类被重写的方法的方法名和形参列表相同 -
权限修饰符
子类重写的方法的权限修饰符 不小于父类被重写的方法的权限修饰符。也就是子类的方法不能低于超类方法的可见性。子类不能重写父类声明为父类private的方法。 -
返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的 -
异常类型
子类重写的方法抛出的异常类型不大于父类被重写的方法提出的异常类型③子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都是static的(不是重写,因为静态的方法不能被覆盖)
- 子类对象实例化的全过程
通过子类构造器创建子类对象时,一定会直接或间接调用父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中的空参构造器为止
正是因为加载过所有的父类的结构,所有才可以看到内存中有父类中的结构,子类对象才可以考虑调用。
创建子类对象时,调用了父类的构造器,但是自始至终就只创建过一个对象,并没有new 父类那些对象。
- 子类对象实例化的全过程
Object:所有类的超类
equals()方法
- Object类的equals方法用于检测一个对象是否等于另外一个对象。
- equals()是方法,因此只能适用于引用数据类型
- Object类中equals的定义 和 == 是一样的
Object类中定义的equals()和==的作用是相同的:比较的是两个对象地址值是否相同,即两个引用是否指向同一个对象实体。 - 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个地址而是内容
- ==和equals()区别
- == 是运算符
- 可以使用在基本数据类型变量和引用数据类型变量中。
- 如果比较的是基本类型数据变量,比较两个变量保存的数据是否相等。(不一定类型要相同)
- 如果比较的是引用类型变量,比较的是两个对象地址值是否相同,即两个引用是否指向同一个对象实体。
- 使用时必须保重左右两边的变量类型一致
-
自定义类重写equals()方法
重写的原则:比较两个对象的实体内容是否相同
如果子类可以有自己相等性的概念,则对称性需求将强制使用getClass检测。(因为如果用otherobject instanceof Employee 的话,这就允许otherobject属于一个子类,即m 那e.equals(m)返回的是true,而m.equals(e)却不能返回true,这就违背了对称性。 而且m和e本来连一个类都不是,按常理来讲就不可能返回true)。
但是如果由超类决定相等性概念,那就可以使用instanceof检测,这样就可以在不同子类的对象之间进行相等性比较。
编写一个完美equals方法的建议:(JAVA核心技术I卷 177页)
-
编译器有自动生成的equals(),真正开发的时候equals都是自动生成的
toString() 方法
- Object类的toString()方法的返回值:
getClass().getName() + '@' + Integer.toHexString(hashCode())
eg:
Customer cust1 = new Customer("Tom",21);
System.out.println(cust1.toString());//com.java1.Customer@12db9742
System.out.println(cust1);//com.java1.Customer@12db9742
- 当我们输出一个对象的引用时,实际上就是调用的当前对象的toString()。所以可以不用写x.toString(),而写作“”+x,这条语句将一个空串与x的字符串表示(x.toString())相连接。 这样即使x是基本类型,这条语句仍然能执行
- 像String、Date、File、包装类等都重写了Object类中的toString()方法。重写以后,返回实体内容信息
- 自定义类重写toString(),返回实体类内容
包装类Wrapper
-
包装类:针对八种基本数据类型定义相应的引用类型,使得基本数据类型的变量具有类的特征
-
基本数据类型、包装类、String三者之间的相互转换
-
基本数据类型转成包装类:
①调用构造器int num1 = 10; Integer in1 = new Integer(num1); //调用Integer构造器 System.out.println(in1.toString());
②自动装箱: 基本数据类型–>包装类
int num2 = 10; Integer in1 = num2//自动装箱
-
包装类转换为基本数据类型:
①调用包装类的xxxValue()Integer in1 = new Integer(12); int i1 = in1.intValue();
②自动拆箱
int num3 = in1;//自动拆箱
-
基本数据类型、包装类–>String类型
①连接运算String str1 = num1+"";
②调用String重载的valueOf(Xxx xxx)
float f1 = 12.3f; String str2 = String.valueOf(f1);//"12.3"
-
String类型–>基本数据类型、包装类
调用包装类的parseXxx(),要确认转的符合要求否则会报NumberFormatExceptionint num2 = Integer.parseInt(str1);
-
拆箱和装箱是编译器要做的工作,而不是虚拟机
-
面试题目
①答案是1.0 因为编译的时候做了类型提升
如果在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double
②Integer内部定义了IntegerCache结构 把经常出现的值包装到了相同对象中。这时候用就是true,但如果没是那些没存的值,就会new new的地址不一样所以用返回的就是false。
因此我们要用equals来判断。
多态
- 理解多态性:可以理解为一个事物的多种形态
- 何为多态性
对象的多态性:父类的引用指向子类的对象(子类的对象赋给父类的引用)。
一个对象变量可以指示多种实际类型的现象称为多态。
在运行时能够自动地选择适当的方法,称为动态绑定。
- 多态的使用:虚拟方法调用
有了对象的多态性后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。即编译看左边,运行看右边 - 多态性使用前提:
①有类的继承关系
②要有方法的重写
Person p2 = new Man();
Person p3 = new Woman();
- 对象的多态性:只适用于方法,不适用于属性。即编译和运行都看左边
向下转型 (强制类型转换) instanceof
- 有了对象多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时智能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
Person p2 = new Man();
p2.earnMoney();//不可以, 只能调person的方法, earnMoney是Man的方法
Man m1 = (Man)p2; //要想调子类的方法,就要强制类型转换
m1.earnMoney(); //可以调
- 如何调用子类特有的属性和方法?
使用强制类型转换,即向下转型
- 使用强转时,可能出现ClassCastException的异常。这就要用到关键字instanceof
a instanceof A ;//判断对象a是否是类A的实例
为了避免在向下转型时出现classCastException的异常,在向下转型之前先进行instanceof的判断,一旦返回true,就向下转型。false 不能向下转型
如果B是A的父类
(a instanceof A)==true
则(a instanceof B)==true
4. 使用强转时常见的问题
- 题目
base.add(1,2,3) 输出的是sub_1, 因为Sub1类是Base1类 add方法的重写, 根据多态性 base调的是Sub1的第一个add方法。因为Sub1的第二个add方法和Base1 的add方法的参数列表不一样,不是方法重写。
s.add(1,2,3) 输出的是sub_2, 这个时候s是一个Sub1类,这时候才会调用第二个add方法
关键字
this
this可以理解为当前对象
- this可以用来修饰属性、方法和构造器
- this修饰属性和方法
在类的方法中,我们可以使用“this”.属性 或 “this”.方法,调用当前对象属性或方法。但通常都省略this。但如果方法的形参和类的属性同名时,必须显示使用“this.变量”的方式,表明是该属性而不是形参。
在类的构造器中, this.属性 调用当前正在创建的对象的属性或方法 - this修饰或调用构造器
在类的构造器中,可以显示的用 this(形参列表)方式,调用本类中指定的其他构造器规定:this(形参列表)必须声明在当前构造器的首行
super
super可以理解为父类的
- super可以用来修饰属性、方法和构造器
- super调父类属性、方法
子类和父类中定义了同名的属性或方法时,要想在子类中调用父类的属性或方法时,要用super.属性 或 super.方法 - super调用构造器
格式:super(形参列表)
在子类的构造器中,可以显示的用 super(形参列表)方式,调用父类中指定的构造器
在类的构造器中,针对super(形参列表) 或this(形参列表) 只能二选一。
在构造器的首行,没有显示声明super(形参列表) 或this(形参列表),则默认是super()。规定:super(形参列表)必须声明在当前构造器的首行。
super和this引用不是类似的概念,super不是一个对象的引用,它只是一个指示编译器调用超类方法的特殊关键字,不能给super赋给另外一个对象变量
m = this;//正确,因为this是当前对象的引用
m = super; //错误,super不是当前对象的引用
instanceof
为了避免在向下转型时出现classCastException的异常,在向下转型之前先进行instanceof的判断,一旦返回true,就向下转型。false 不能向下转型
x instanceof A ;//判断对象a是否是类A的实例
static
我们有时候希望无论是否产生了对象或产生多少对象的情况下,某些特定的数据在内存空间里只有一份。
- static 是静态的,可以用来修饰属性、方法、内部类、代码块
- 使用static 修饰属性:静态变量
按是否用static修饰属性分为静态属性(类变量)vs非静态属性(实例变量)- 非静态变量
创建类的多个对象,每个对象都拥有一套类中的非静态属性。修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。 - 静态变量
创建类的多个对象,多个对象都共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过的。
静态变量随着类的加载而加载,它属于类,而不属于任何单个的对象。可以通过“类.属性”的方式进行调用。
静态变量在内存中只会存在一份,存在方法区的静态域中。
- 非静态变量
属性可以被多个对象所共享,不会随着对象的不同而不同,我们就可以用static修饰。
- 使用static修饰静态常量
静态变量使用较少,静态常量很常用。 如
public static final double PI = 3.141592653
System类声明的静态常量out,我们打印用的System.out 其实就是用类直接掉out常量了public class System{ public static final PrintStream out = ...; }
- 使用static修饰方法
随着类的加载而加载,可以通过“类.静态方法”的方式进行调用。
静态方法中只能调用静态的方法或属性;非静态方法中可以调
静态方法不能调用this、super下面两种情况可以使用静态方法:
方法不需要访问对象状态,比如工具类中的方法(Math.pow),它需要的所有参数都通过显示参数提供
方法只需要访问类的静态字段
final
final 最终的
- final可以用来修饰:类、方法、变量
- final修饰类:此类不能被其他类所继承
如:String类、System类、StringBuffer类
将一个类声明为final,只有其中的方法自动地称为final,而不包括字段 - final修饰方法:此方法不能被重写
如:Object类的getClass() - final 修饰变量:此时的“变量”称为常量
- final修饰属性时
属性必须在构造对象时初始化(也就是构造器执行后,这个字段的值已经设置),因此可以考虑赋值的位置:显示初始化,代码块中赋值,构造器中赋值 - final修饰局部变量
尤其是用final修饰形参时,表明此形参是一个常量,当调用此方法时,给常量形参赋一个实参。一旦赋值以后,就不能再修改只能使用。
- final修饰属性时
- static final 用来修饰属性、方法
用来修饰属性: 全局常量
abstract
abstract:抽象的
- abstract可以用来修饰:类、方法
- abstract修饰类: 抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化调用
- 开发中,都会提供抽象类的子类,让子类对象实例化
- abstract修饰方法:抽象方法
- 抽象方法只有声明 没有方法体
public abstract void eat();
- 包含抽象方法的类,一定是个抽象类;反之,抽象类中可以没有抽象方法。
- 子类重写了父类中所有的抽象方法后,此子类可实例化。若没有重写父类中所有的抽象方法,则子类也是抽象类。
- abstract不能修饰私有方法、静态方法、final的类
- 抽象类的匿名子类
- 多态的应用:模版方法设计模式
interface
接口可以达到多重继承的效果
- java中,接口和类是并列的两个结构
- 如何定义接口:定义接口中的成员
-
JDK7及以前,只能定义全局常量和抽象方法
全局常量:public static final的,但书写时可以省略不写
抽象方法:public abstract
-
JDK8 新特性:除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
接口中定义的静态方法只能由接口调用。
在子类(实现类)调用接口中静态的方法:
CompareA..method1()
通过实现类的对象,可以调用接口中的默认方法;如果实现类重写了接口中的默认方法,调用时调用的是重写后的方法。
在子类(实现类)调用接口中默认的方法:
CompareA.super.method3()
如果子类继承的父类和实现的接口中声明了同名同参数的方法,那么在子类方法没有重写的情况下,调用的是继承的父类的方法,类优先原则。
- 接口中不能定义构造器,意味着接口不可以实例化
- Java开发中,接口通过让类去实现(implements)
class Plane implements Flyable
如果实现类覆盖了接口中所有抽象方法,则此实现类就可以实例化。
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
5. Java类可以实现多个接口—>弥补了类的单继承性
格式:class AA extends BB implements CC,DD,EE
6. 接口和接口之间 可以继承,且可以多继承
- 接口的具体使用,体现多态性
- 接口,实际上可以看做是一种规范。相当于规定了做的话该怎么做,但不会规定具体怎么做。
- 接口的应用:代理模式(Proxy)
- 接口的应用:工厂模式
工厂是专门用来造对象的
package
- 为了更好实现项目类的管理,提出包package的概念
- 使用package声明类或接口所属的包,声明在源文件的首行
- 包属于标识符;每“.”一次,就代表一层文件目录
- 同一个包下不能命名同名的接口、类;不同的包下可以
import
- 在源文件显示地使用import结构导入指定包下的类、接口
- 可以使用xxx.* 表示导入这个包下的所有结构。但如果使用xxx子包下的结构,仍需要显示调用。
- 使用的类或接口是在java.lang包下定义的,则可省略import
- 如果源文件中使用了不同包下的同名类,则必须至少有一个用全类名的方式显示
- import static导入指定类或接口中的静态结构