Java面向对象基础
类与对象
在一个Java程序里面,是由多个类(class)构成的,类是由属性和方法组成的,即类=属性+方法(属性就是变量)
定义类
类的修饰符 class 类名 extends 父对象名称 implements 接口名称 {
类体,成员变量和方法组成
}
成员变量与局部变量
- 成员变量:写在类体的里面,方法体的外面,声明时可以不进行初始化值,可以被本类或其他类的方法进行调用。
- 局部变量:写在方法体的里面,声明时必须进行初始化,只能在声明局部变量的方法内进行调用。
- 在一个类中,成员变量和局部变量是可以同名的,同名时会优先使用局部变量,即就近原则
/**
* 局部变量与成员变量
*/
public class Test {
//成员变量,在整个类中有效成员变量,有默认值
int i;//0
public void m1(){
//局部变量 如果成员变量和局部变量出现了命名冲突,在使用的时候局部优先
//局部变量在使用的时候 必须先声明,后赋值,再使用
int i = 30;
System.out.println(i);//30
System.out.println(this.i);//0
}
public static void main(String[] args) { //静态方法调用非静态方法要初始化类
Test t = new Test ();
System.out.println(t.i);//0
t.m1();
}
}
数据类型的默认值
byte,short,int,long 0
float,double 0.0
boolean false
char \u0000
引用数据类型的默认值:null
对象内存
在JVM内存里面主要分布有以下三个区域:
- 栈:存放基础数据和自定义对象的引用(Student s = new Student()会在堆里面开辟一块空间来存储创建的Student对象,假设内存地址是0x0101,那么s就会指向这个内存地址)
- 堆:主要存储创建的对象,即new出来的对象。(通过s.的方式访问堆中的对象,并为其成员变量进行赋值)
- 方法区:加载存放class文件(字节码文件),存放static变量
空指针异常:如果堆里面的对象没有被指向,Java里面有个垃圾回收器会将对象进行回收。开发时不需要手动设置为null,垃圾回收器会自动回收。
public class Test01{
public static void main(String[] args){
Student s = new Student();
s.name = "张三";
s.age = 20;
s.sex = true;
s.id = 1001;
System.out.println(s.name);//张三
s = null;//将s设置为null
System.out.println(s.name);//报NullPointerException(空指针异常)
}
}
构造方法
构造方法的作用是给对象数据进行初始化。
构造方法格式特点:
- 方法名与类名相同(注意大小写也要与类名一致)
- 没有返回值类型
- 没有void修饰
- 没有具体的返回值return;
- 如果一个类没有提供任何构造方法,系统默认提供无参数构造方法
- 如果一个类已经手动的提供了构造方法,那么系统不会再提供任何构造方法。(如果还想使用无参构造方法,就必须自己写出无参构造方法)
注意: 不能手动调用构造方法
无参构造方法
public class User{
//定义无参构造方法
User(){
System.out.println("User的无参数构造方法执行!");
}
}
public class Test01{
public static void main(String[] args){
User u = new User();
//u.User();不能手动调用构造方法
}
}
上面代码在创建User对象的时候,就打印出了构造方法里面的语句,说明在创建对象的时候会默认执行无参构造方法。构造方法不能手动调用。
有参构造方法
public class User{
private String name;
private int age;
//定义有参构造方法,在创建对象的时候为成员变量赋值
User(String _name,int _age){
name = _name;
age = _age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
public class UserTest01{
public static void main(String[] args){
//调用了有参构造方法
User u = new User("化腾",40);
System.out.println(u.getName());
System.out.println(u.getAge());
}
}
通过调用有参的构造方法将数据传入,构造方法内部再将这些数据赋值给成员变量,这样在创建好对象之后里面的属性就有值了。
//手动编写带有参数的构造方法,系统将不会提供默认的无参构造方法
public class UserTest01{
public static void main(String[] args){
User u = new User();//报错
}
}
注意:建议在提供有参构造方法之后自己再提供无参构造方法
this关键字
this是java里面的一个关键字,是一种引用类型,在堆(heap)中的每个java对象上都有一个this指向自己。this代表着当前对象的引用。
this可以做什么?
- 可以区分成员变量和局部变量。(如果有相同的标识符,则可以通过this调用成员变量)
- 可以调用构造方法(this(实参);)
/*
区分成员变量和局部变量
*/
class Test{
private String name;
/*
因为this代表当前对象的引用,所以可以使用this.变量名的方式调用成员变量
*/
public void setName(String name){
//this.name表示的是成员变量,name表示传入的参数
this.name = name;
}
public String getName(){
//return name;
return this.name;
}
//还可以使用this.方法名的方式调用当前对象的方法
//下面两种方式都可以调用到m2方法
public void m1(){
this.m2();
m2();
}
public void m2(){
System.out.println("TESTING");
}
}
有相同的标识符,则可以通过this调用成员变量
public class ActorTest01 {
public static void main(String[] args){
Actor a = new Actor();
a.setName("范冰冰");
a.act1();
a.act2();
}
}
class Actor{
private String name;
public void act1(){
//定义一个局部变量name
String name = "周润发";//Java就近原则
System.out.println("name=" + name);//这里打印出来的是局部变量name的值 (周润发)
System.out.println("this.name=" + this.name); //(范冰冰)
}
public void act2(){
System.out.println("name=" + name);//这里打印的是成员变量name的值 (范冰冰)
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
调用构造方法时(this(实参);)
注意:如果要是使用this调用构造方法的话,那么this必须出现在构造方法的第一行。
/*
调用构造方法
*/
class MyDate{
private int year;//年
private int month; //月
private int day; //日
//构造方法
//需求:在创建日期对象的时候,默认的日期是:1970-1-1
MyDate(){
//通过this调用有参的构造方法
this(1970,1,1);//必须出现在第一行,否则将编译报错
//构造方法不能这样调用
//MyDate(1970,1,1);Error
}
//构造方法
MyDate(int year,int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
//set和get方法
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
}
public class ThisTest01{
public static void main(String[] args){
MyDate md = new MyDate();
System.out.println(md.getYear()+"年"+md.getMonth()+"月"+md.getDay()+"日");
}
}
static关键字
static可以修饰什么?
- static可以修饰变量,静态变量在类初始化阶段赋值,并且只赋值一次,当一个变量可以被一个类的多个对象共享时,可以将该变量设置为静态变量(如圆周率PI)。
- static可以修饰方法,不用创建对象就能直接访问该方法,即使用类名.静态方法名的方式。在定义一些工具方法的时候可以将其声明为静态方法
- static可以定义静态语句块,静态语句块在类初始化阶段执行,并且只执行一次,并且是自上而下的顺序执行,在构造方法之前执行。
静态变量:
- 可以通过对象名.静态方法名的方式访问,工作中不这样使用
- 可以通过类名.静态方法名的方式访问,工作中使用这种写法
- 不会报空指针,说明静态变量跟对象无关
public class StaticTest01 {
public static void main(String[] args){
Employee e = new Employee();
//可以通过对象名.静态方法名的方式访问,工作中不这样使用
System.out.println(e.company);
//可以通过类名.静态方法名的方式访问,工作中使用这种写法
System.out.println(Employee.company);
e = null;
System.out.println(e.company);//不会报空指针,说明静态变量跟对象无关。
}
}
class Employee{
private String name;
static String company = "阿里巴巴";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
静态方法:
- 静态方法可以直接调用静态方法(不用创建对象就能能直接访问该方法,即使用类名.静态方法名的方式。)
- 静态方法中不能访问非静态的方法
- 静态方法中不能访问非静态的变量
- 不会报出空指针异常
- 静态方法不能使用this
class StaticTest02{
//成员变量
int i;
//成员方法
//成员方法必须使用“引用.”调用
public void m1(){
System.out.println("m1方法");
}
//静态方法
//可以使用“类名.”方式调用.也可以用“引用.”,即使用的是“引用.”,底层还是用的“类名.”
//静态方法中不能直接访问非静态数据.
//静态方法中不能使用this
public static void m2(){
//m1();错误,静态方法中不能访问非静态的方法
//System.out.println(i);错误,静态方法中不能访问非静态的变量
System.out.println("m2方法");
}
public static void main(String[] args){
StaticTest02 st = new StaticTest02();
st.m1();
m2();//静态方法可以直接调用静态方法
st.m2();//不建议这样使用,因为静态方法跟对象无关
StaticTest02.m2();//建议这样使用
st = null;
st.m2(); //不会报出空指针异常
}
}
静态语句块:
- 静态代码块的执行顺序是自上而下
- 静态语句块只执行一次
- 在构造方法之前执行
public class StaticTest03{ //加载方法区
//静态语句块
static{ //加载方法区
System.out.println("静态语句块1");
}
static{ //加载方法区
System.out.println("静态语句块2");
}
static{ //加载方法区
System.out.println("静态语句块3");
}
//构造方法
StaticTest03(){
System.out.println("构造方法");
}
public static void main(String[] args){
System.out.println("main main方法 1");
System.out.println("main main方法 2");
new StaticTest03();
new StaticTest03();//静态语句块只执行了一次
}
}
/**
执行结果:
静态语句块1
静态语句块2
静态语句块3
main main方法 1
main main方法 2
构造方法
构造方法
*/
注意:
- 在java中的main方法就是静态方法,之所以被设计为静态方法的原因就是无需对象,可以直接运行main方法。
- 静态方法是不能被重写的,虽然写出重写的代码是可以看到重写的效果,但是加入@Override注解会报错,因此静态方法不能被重写。
- 静态方法中不能使用this,this指向当前对象,static又跟对象无关,因此static和this是互斥的。
- 静态方法中不能直接访问实例方法和实例变量,后面两者是跟对象有关的。
- 静态方法中只能直接访问静态属性和静态方法,
- 实例方法既可以访问静态的属性和方法又能访问非静态的属性和方法。
静态导入:
静态导入在实际应用中使用的很少。当想要打印很多内容的时候使用静态导入
/**
public class StaticImport {
public static void main(String[] args) {
System.out.println("我想学习java");
System.out.println("我想喝口水");
System.out.println("我想歇一会");
System.out.println("我想放弃");
}
}
*/
//上面代码中出现了很多System,此时可以使用静态导入简化代码:
import static java.lang.System.out;//静态导入
public class StaticImport {
public static void main(String[] args) {
//在System类中,out是静态变量可以将其进行静态导入,在编码的时候可以直接使用out了
out.println("我想学习java");
out.println("我想喝口水");
out.println("我想歇一会");
out.println("我想放弃");
}
}
代码块
- 局部代码块,在方法中出现,限定变量生命周期,及早释放,提高内存利用率
- 构造代码块,在类中的方法外的{}叫做构造代码块,在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行
- 静态代码块, 在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且只执行一次。一般用于加载驱动。
- 同步代码块(后面多线程部分会讲解)
方法执行顺序:
- 1.静态代码块,随着类加载而加载,且只执行一次
- 2.构造代码块,每创建一个对象就会执行一次,优先于构造方法执行
- 3.构造方法,每创建一个对象就会执行一次
继承(extends)
java语言里面只支持单继承,子类可以继承父类中的非private修饰的成员方法和成员变量,构造方法不能被继承。如果一个类没有显示的继承其他类,那么这个类会默认继承Object类
继承的优点
提高了代码的复用性
增强了代码的可维护性
让类与类之间产生了关系,是多态的前提
public class SuperClass{
public void m1(){
System.out.println("SuperClass中的m1方法");
}
private void m2(){
System.out.println("SuperClass类中的m2方法");
}
public class SubClass extends SuperClass{
public void m3(){
System.out.println("SubClass中的m3方法");
}
}
public class Test01{
public static void main(String[] args){
SubClass s = new SubClass();
//因为SubClass继承了SuperClass,所以可以在子类里面调用父类的方法
s.m1();
//s.m2();//子类不能访问父类中private修饰的方法
//SubClass中自己的方法
s.m3();
}
}
Java不支持多继承,但是支持多重继承,子类可以直接访问其先辈类里面的非private修饰的方法和属性。
在子类中如果要访问父类的private方法或属性的时候,需要在父类中提供get方法,通过get方法间接访问这些内容。
//父类
class Father {
//父类中的私有属性
private int age;
public int getAge() {
return age;
}
}
//子类
class Son extends Father{
}
//测试类
class Test03{
public static void main(String[] args) {
Son son = new Son();
son.getAge();//这样就可以利用get方法间接访问父类中private修饰的age了
}
}
父类中的private修饰的属性是不能被子类继承的,倘若父类中提供了public或者protected修饰的方法来访问该属性,比如set,get方法,这样在子类中是可以通过方法来使用该private属性的。
方法的重写(override)
当父类中的方法无法满足子类需求时,子类可以将父类的方法进行重写来满足需求。
- 发生在两个类中,且两个类必须是继承关系
- 必须具有相同的方法名,相同的返回值类型,相同的参数列表.
- 子类重写的方法不能比父类被重写的方法拥有更低的访问权限。
- 子类重写的方法不能比父类被重写的方法抛出更宽泛的异常。(关于异常后面的章节再讲。)
- 私有的方法不能被重写。
- 构造方法无法被重写,因为构造方法无法被继承。
- 静态的方法不存在重写。
- 重写指的是成员方法,和成员变量无关。
super关键字
super代表的是当前子类对象中的父类型特征,super并不是指的父类对象。
super使用场景:
- 子类和父类中有同名的属性,例如都有name这个属性。在子类中默认会使用其自己的name属性,如果要访问父类中的name属性,需要使用super。
- 子类重写了父类的某个方法(假设这个方法名叫m),如果在子类中需要调用父类中的m方法时,需要使用super。
- 子类调用父类中的构造方法时,需要使用super。
- 注意:super不能用在静态方法中。
class Animal {
public String name = "动物";
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Rabbit extends Animal {
public String name = "萌仔";
public void eat() {
System.out.println("吃萝卜");
}
public void info(){
System.out.println(super.name);//获取父类中的name变量
System.out.println(this.name);//可以不加this,系统默认调用子类自己的name
super.eat();//调用父类中的eat方法
this.eat();//调用自己重写的eat方法,可以不加this,默认调用自己的eat方法
//eat();
}
}
public class AnimalTest01 {
public static void main(String[] args) {
Rabbit r = new Rabbit();
r.info();
}
}
class Animal {
//颜色
private String color;
//品种
private String category;
//构造方法
public Animal(){
System.out.println("Animal中的构造方法");
}
public Animal(String color,String category){
this.color = color;
this.category = category;
}
//get、set方法
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
class Rabbit extends Animal {
public Rabbit(){
//手动调用父类中的有参构造方法给成员变量进行赋值
super("白色","长耳兔");
System.out.println("Rabbit中的构造方法");
}
}
public class Test {
public static void main(String[] args) {
Rabbit r = new Rabbit();
System.out.println(r.getColor());
System.out.println(r.getCategory());
}
}
//Rabbit中的构造方法
//白色
//长耳兔
//没有调用super系统自动调用super 构造函数
/*private String color="白色";
//品种
private String category="长耳兔";
*/
//Animal中的构造方法
//Rabbit中的构造方法
//白色
//长耳兔
注意:一个构造方法第一行如果没有this(…);也没有显示的去调用super(…);系统会默认调用super();如果已经有this了,那么就不会调用super了,super(…);的调用只能放在构造方法的第一行,只是调用了父类中的构造方法,但是并不会创建父类的对象。
super和this的对比
- this:代表当前对象的引用
- super:代表的是当前子类对象中的父类型特征
this和super的使用区别
-
调用成员变量
this.成员变量: 调用本类的成员变量
super.成员变量: 调用父类的成员变量 -
调用构造方法
this(…) :调用本类的构造方法
super(…):调用父类的构造方法 -
调用成员方法
this.成员方法:调用本类的成员方法
super.成员方法:调用父类的成员方法
多态
- 有继承
- 有方法重写
- 有父类引用指向子类对象
特点
- 特点1:多态对象不能访问子类特有成员
- 特点2:多态对象被重新定义的属性表现的是父类中的数据
- 特点3:多态对象被重写的方法表现的是子类中定义的方法
- 总而言之:多态对象除了重写的方法 其他和父类对象完全相同
静态绑定和动态绑定
通过上面示例代码可以看出来,等号左边的内容表示是父类的引用,该引用是在栈内存中,指向了一块堆中的地址,而这个地址中存放的是其子类的对象。上面的代码中,a1是Animal类型的一个引用,指向的是其子类Cat的对象,这个就叫做父类引用指向子类对象。程序在编译(javac)的时候a1被看做Animal类型,所以a1.eat()绑定的是Animal类中的eat()方法,这叫做静态绑定,程序运行时,会在堆中开辟空间创建出对象,此时a1指向的是堆中的Cat对象,而在Cat中对eat()方法进行了重写,所以在运行阶段绑定的是Cat中的eat()方法,这叫做动态绑定。
- 编译看左,运行看右。编译的时候看等号左边的类型,运行的时候看等号右边的类型。
public class Animal {
public int num = 10;
public void eat(String name) {
System.out.println("动物在吃饭");
}
public void move(){
System.out.println("动物在走路!");
}
}
public class Dog extends Animal{
public void eat(String name ) {
System.out.println(name+"在吃");
}
//继承
public class Cat extends Animal {
public int num = 20;
//重写
public void eat(String name) {
System.out.println(name+"在吃");
}
}
//Cat特有的方法
public void say() {
System.out.println("猫在叫");
}
}
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Animal a = new Cat();//父类引用指向子类对象,我指着这只猫说这是一只动物
System.out.println(a.num);//因为成员变量不存在重写,所以结果是10
a.eat("小花");//会调用Cat中重写的eat方法
a.move();
//如果要是想执行Cat里面的move方法该怎么办?
//只能强制类型转换,需要加强制类型转换符
Cat c = (Cat)a;
c.say();
Animal d = new Dog();
System.out.println(d.num);
d.eat("阿黄");//会调用Dog中重写的eat方法
//强制类型转换
//Cat cd = (Cat)d; //会报错 java.lang.ClassCastException
}
instanceof关键字
上面的代码里面将一个指向Dog对象的Animal引用d进行强制转换成Cat类型时报出了ClassCastException类转型异常,出现的原因就是堆中的对象是Dog,要将其转换为Cat,这显然是不行的。开发中要是想避免这种错误需要使用instanceof来判断一下。
- instanceof语法
引用 instanceof 类型
会判断引用指向的对象是否是该类型的,如果是则计算结果是true,否则结果是false
//Cat cd = (Cat)d;
if(d instanceof Cat) {
Cat cd = (Cat)d;
}else {
System.out.println("不能强制转换");
}
例子
public class Lamp {
public void On() {
System.out.println("开灯");
}
}
public class GreenLamp extends Lamp{
public void On() {
System.out.println("开绿灯,绿灯亮");
}
}
public class RedLamp extends Lamp{
public void On() {
System.out.println("开红灯,红灯亮");
}
}
public class Person {
public void action(Lamp l) {
l.On();
}
}
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
RedLamp r = new RedLamp();
GreenLamp g = new GreenLamp();
Person p = new Person();
p.action(r);//开红灯,红灯亮
p.action(g);//开绿灯,绿灯亮
}
}
在工作当中尽量面向抽象编程,不要面向具体编程。在上面实例中Lamp方法的参数On相比于RedLamp和GreenLamp更加抽象一些.程序的扩展性更强了,只要是灯都能传过来打开。
finall关键字
- final修饰的类无法被继承,比如字符串String就是final修饰的,我们不能创建其子类。
final class A{}
class B extends A{}//error无法继承
- final修饰的方法无法被重写,当某个方法不希望被子类重写的时候,需要将其声明为final修饰。
class A{
public final void m1(){}
}
class B extends A{
public void m1(){}//error无法重写
}
- final修饰的局部变量,一旦赋值,不可再改变,使用较少。
- final修饰的成员变量必须初始化值,在定义那些不常改变或者永恒不变数据的时候使用,比如定义圆周率
//final修饰的成员变量一般和static联用。
java规范中要求所有的常量"大写"
public static final double PI = 3.14;
final修饰引用类型
final修饰的引用类型,该引用不可再重新指向其他的java对象。但是final修饰的引用,该引用指向的对象的属性值是可以修改的。
- 基本类型,是值不能被改变
- 引用类型,是地址值不能被改变,对象中的属性可以改变
final Customer c = new Customer("张三",20);
//c是final的,无法重新赋值。
//c = new Customer("李四",21);//Error
c.setName("王五");
c.setAge(25);
System.out.println(c.getName());
System.out.println(c.getAge());
抽象类
抽象类的特点
- 抽象类无法被实例化,无法创建抽象类的对象。
- 虽然抽象类没有办法实例化,但是抽象类也有构造方法,该构造方法是给子类创建对象用的。这也算是多态的一种。
- 抽象类中不一定有抽象方法,但抽象方法必须出现在抽象类中。
- 抽象类中的子类可以是抽象类,如果不是抽象类的话必须对抽象类中的抽象方法进行重写。
- 抽象类和抽象方法不能被final修饰
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
public abstract class Vehicle {
public abstract void start() ;
public abstract void stop();
}
public class Bike extends Vehicle{
//必须要重写抽象类中的所有方法
public void start() {
System.out.println("骑自行车");
}
public void stop() {
System.out.println("停自行车");
}
}
public class Car extends Vehicle{
public void start() {
System.out.println("开轿车");
}
public void stop() {
System.out.println("停轿车");
}
}
public class Person {
public void goWork(Vehicle v) {
v.start();
}
public void goHome(Vehicle v) {
v.stop();
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person();
Vehicle b = new Bike();
Vehicle c = new Car();
p.goHome(b);
p.goWork(b);
System.out.println("换车");
p.goHome(c);
p.goWork(c);
//停自行车
//骑自行车
//换车
//停轿车
//开轿车
}
}
Object类中的方法
toString方法
Object类中设计toString方法的目的是返回java对象的字符串表示形式。
通过源码可以看到Object类中的toString方法是这样的:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
如果直接打印一个引用数据类型,系统会默认调用其toString方法。上面的结果中打印的是对象的内存地址,但有时我们希望打印出该对象中属性的值,此时就是父类方法无法满足子类场景,需要重写toString方法。
public class Test {
public static void main(String[] args) {
Person p = new Person("张三",20);
String n = "10";
System.out.println(n.toString());//10
System.out.println(p.toString());//overrideToStringTest.Person@7852e922
}
}
private String name;
private int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
重写toString
//重写toString 类
public String toString() {
return "person name:"+name;
}
Person p = new Person("张三",20);
String n = "10";
System.out.println(n.toString());//10
System.out.println(p.toString());//person name:张三