面向对象概述
概述
编程语言是人类创造的,因此里面很多思想来源于生活。程序员使用编程技术开发软件也是为了解决生活中一些繁琐的问题,这一段内容将要学习一种编程思想-面向对象,Java就是一门面向对象设计的语言。
面向过程和面向对象的区别
通过对比,来更深入的了解下面向对象
-
面向过程主要是把问题分解成多个不同的步骤,然后把各个步骤变成方法,它更强调过程
-
面向对象会把问题分解成各个对象,然后各个对象之间进行交互,每个对象内部封装了进行了封装
-
举例说明,比如吃烧烤
面向过程:
去买羊肉、羊腰、签子、木炭、烤炉、调味料
腌制羊肉、穿羊肉
将木炭放到烤炉里面并引燃
烧烤
开吃
收拾
小提示:需要一步一步去做
面向对象:
去烧烤店
跟服务员点菜,20串羊肉、10个大腰子
开吃
跟收银员结账
去烧烤店里面,找服务员点菜,烧烤的事交给别的对象去做
小提示:面向过程注重过程,面向对象注重对象之间的交互。面向对象将复杂的事情简单化,我们从行动者变成了指挥者。面向对象开发就是去创建对象,指挥对象做事情。
面向对象三大特征
- 封装(encapsulation)
- 继承(inherit)
- 多态(polymorphism)
类与对象
属性和行为
我们学习编程最主要的一个目的就是解决日常生活中的问题,将繁琐的事物交给计算机去处理。这就会需要程序员将日常生活中的事物进行抽象,然后在使用编程语言编写出来。
如何将日常生活中的事物进行抽象呢?一般事物都具有下面两个特点:
- 属性-描述该事物的信息,一般是事物上面的名词
- 行为-描述该事物能做什么,一般是动词
比如学生的属性:
学号:1001
姓名:张三
性别:男
年龄:20
学生的行为:
学习:好好学习,天天向上
谈恋爱:找个会写代码的妹子
做运动:打篮球
类
在Java语言里面,最基本的单位是类(class),类是由成员变量和方法组成的,类=成员变量+方法
- 成员变量:在类的内部,方法的外部定义的变量叫做成员变量。【全局变量】
- 局部变量:在方法内部定义的变量叫做局部变量。
之前我们使用的都是局部变量
我们可以将现实生活中的事物抽象成一个类,通过操作这个类来解决问题。
- 如何定义类?
类的修饰符 class 类名 extends 父对象名称 implements 接口名称 {
类体,成员变量和方法组成
}
- extends和implements还没有学过,目前我们定义类的格式是这样的:
类的修饰符 class 类名{
类体,成员变量和方法组成
}
这里根据上面分析的学生来定义一个类
class Student{
//成员变量可以不初始化
//学号
int id;
//姓名
String name;
//性别
boolean sex;
//年龄
int age;
//这里面的方法可以不加static修饰,关于static后面会详细讲解
public void study() { //定义学习的方法
System.out.println("学生学习");
}
public void love(String name) { //定义谈恋爱的方法
System.out.println("我在跟" + name + "谈恋爱");
}
public void takeExercises(String sport){
System.out.println(sport);
}
}
- 学生类定义好了,那如何表示上面的张三这个人呢?这时需要使用对象
类和对象
类是一组相关的属性和行为的集合,类一般都是泛指某一种事物,对象就是该事物的具体体现。
-
比如:
-
类-学生
-
对象-张三
当然,对象可以是李四,王五,赵六,只要是学生就行。
再比如:
类-歌手
对象-周杰伦,刘德华
类-运动员
对象-姚明,刘翔
对象的创建和使用
通过创建对象的方式,将张三表示出来
public class StudentTest01{
public static void main(String[] args){
//创建对象的格式:类名 对象名 = new 类名();
//对象名:是合法的标识符即可,建议使用驼峰命名法
Student s = new Student();
//Student s1 = new Student();一个类可以创建多个对象,李四、王五、赵六等
//通过对象名.变量名的方式使用成员变量
s.name = "张三";//给学生名字赋值
s.age = 20;
s.sex = true;
s.id = 1001;
System.out.println(s.id);
System.out.println(s.name);
System.out.println(s.age);
System.out.println(s.sex ? "男":"女");
//通过对象名.方法名(...)的方式调用方法
s.study();
s.love("赵六");
s.takeExercises("打篮球");
}
}
成员变量和局部变量
成员变量和局部变量的区别
之前简单的介绍过这两种变量,这一节里面,我们来详细的分析一下两者的区别
成员变量:写在类体的里面,方法体的外面,声明时可以不进行初始化值,可以被本类或其他类的方法进行调用。
局部变量:写在方法体的里面,声明时可以不初始化,但是在使用前一定要初始化,只能在声明局部变量的方法内进行调用。
数据类型的默认值
如果只声明成员变量不对其进行赋值,那么Java里面的8个基本数据类型的默认值都是什么呢?
请看如下代码:
public class Demo1 {
byte b;
short s;
char c;
int i;
long l;
float f;
double d;
boolean boo;
String str;
public static void main(String[] args) {
Demo1 var = new Demo1();
System.out.println(var.b);//0
System.out.println(var.s);//0
System.out.println(var.i);//0
System.out.println(var.l);//0
System.out.println(var.f);//0.0
System.out.println(var.d);//0.0
System.out.println(var.boo);//false
System.out.println(var.str);//null
System.out.print(var.c);//char的默认值是\u0000
}
}
基本数据类型的默认值
byte,short,int,long 0
float,double 0.0
boolean false
char \u0000
小提示:引用数据类型的默认值:null
对象内存图解【了解】
创建一个对象
public class Student{
//学号
int id;
//姓名
String name;
//性别
boolean sex;
//年龄
int age;
public void study() { //定义学习的方法
System.out.println("学生学习");
}
public void love(String name) { //定义谈恋爱的方法
System.out.println("我在跟" + name + "谈恋爱");
}
public void takeExercises(String sport){
System.out.println(sport);
}
}
学生测试类:
public class StudentTest01{
public static void main(String[] args){
Student s = new Student();
s.name = "张三";
s.age = 20;
s.sex = true;
s.id = 1001;
s.study();
s.love("赵六");
s.takeExercises("打篮球");
}
}
在JVM内存里面主要分布有以下三个区域:
- 栈:存放基础数据和自定义对象的引用
- 堆:主要存储创建的对象,即new出来的对象。
- 方法区:加载存放class文件(字节码文件)
程序是这样执行的:
- 将StudentTest01.class文件加载到方法区
- 将Student.class文件加载到方法区
- main方法压栈
- 在堆里面创建一个Student的对象
- 栈里面的s指向堆里面Student对象的内存地址0x0101
- 给对象的成员变量进行赋值
- 方法弹栈
- 程序执行结束
小提示:这里面主要说明的一点就是图中的实线,这个实线表示的是s指向Student对象堆内存的地址,s本身并不是对象,而是一个指向。比如酒店里面的房间号1024,1024本身不是房间,只是指向了这个房间的号码,方便住宿人员快速找到该房间。
创建两个对象
public class StudentTest01{
public static void main(String[] args){
Student s = new Student();
s.name = "张三";
s.age = 20;
s.sex = true;
s.id = 1001;
Student s1 = new Student();
s1.id=1002;
s1.name="李四";
s1.age=21;
s1.sex=false;
}
}
三个指向两个对象
public class StudentTest01{
public static void main(String[] args){
Student s = new Student();
s.name = "张三";
s.age = 20;
s.sex = true;
s.id = 1001;
Student s1 = new Student();
s1.id=1002;
s1.name="李四";
s1.age=21;
s1.sex=false;
Student s2 = s1;
}
}
小提示:上面代码将s1指向的对象赋给s2,相当于s1和s2指向同一个对象
取消指向
public class StudentTest01{
public static void main(String[] args){
Student s = new Student();
s.name = "张三";
s.age = 20;
s.sex = true;
s.id = 1001;
s = null;//将s设置为null
//s.study();报出NullPointerException(空指针异常)
}
}
小提示:如果堆里面的对象没有被指向,Java里面有个垃圾回收器会将对象进行回收。开发时不需要手动设置为null,垃圾回收器会自动回收。
封装性和private关键字
封装
这一节来看下面向对象三大特征之一的封装。
什么是封装?
封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
封装的优点:
隐藏代码的实现细节,提高安全性。
举个例子,我们日常生活中的插座,如果没有封装外壳直接使用零线火线的话会比较危险,加上外壳封装之后使用起来既方便又安全
private关键字
看看下面代码中所描述的问题。
class Person {
String name;//姓名
int age;//年龄
}
- 上面定义了一个Person类,下面写一个PersonTest01类对其进行测试
public class PersonTest01{
public static void main(String[] args){
//1.创建Person类型的对象
Person p = new Person();
System.out.println(p.age); //0
p.age = -10;//这个地方的数据不合理
}
}
小提示:上面的PersonTest01中人的年龄出现了-10,这显然是一个不合法的数据,所以应该将Person类中的age隐藏起来,不能让外界直接访问,需要使用private修饰符将age进行封装。
class Person {
private String name;
private int age;
/*
添加private修饰之后,外界无法直接访问age了。
这时需要提供两个方法供外界访问即set方法和get方法。
*/
//对外提供两个公开的方法。
//set 赋值
public void setAge(int _age){
//安全控制
if(_age<0 || _age>120){
System.out.println("年龄不合法");
return;
}
age = _age;
}
//get 读取
public int getAge(){
return age;
}
public void setName(String _name){
name = _name;
}
public String getName(){
return name;
}
}
- 创建测试类
public class PersonTest02{
public static void main(String[] args){
//1.创建Person类型的对象
Person p = new Person();
p.setName("郝仁");
p.setAge(20);
System.out.println(p.getName());
System.out.println(p.getAge());
}
}
private使用总结:
- 将成员变量用private修饰
- 提供对应的getXxx()和setXxx()方法
- private仅仅是封装的一种体现形式,封装不是私有
方法调用时参数传递的问题
小提示:方法调用时,参数除了可以传递基本数据类型,还可以传递引用数据类型,这两种参数在传递的时候会有一些差异,在使用的时候需要注意
基本数据类型的参数传递
- 先来看看这段代码,m1方法中的i和main方法里面的i的值分别是多少?
public class Demo1{
public static void m1(int i){
i++;
System.out.println("m1里面的变量i=" + i);
}
public static void main(String[] args){
//局部变量
int i = 10;
//调用
m1(i);
System.out.println("main里面的变量i=" + i);
}
}
- 打印的结果是:
m1里面的变量i=11
main里面的变量i=10
出现这个结果的原因就是main方法里面的i和m1方法里面的i分别占用的是两块不同的内存,请看下图,main方法栈帧和m1方法栈帧分别有两个i,所以这两个i是不同的。
引用数据类型的参数传递
什么是引用数据类型?
在Java语言里面除了基本数据类型,其他的都是引用数据类型。当方法传递的参数是引用数据类型时会是什么样子呢?请看下面代码
- 定义一个Animal类
public
class Animal{
private int age;
public void setAge(int _age){
age = _age;
}
public int getAge(){
return age;
}
}
- 定义一个Animal测试类,请问下面程序打印的结果是什么?
public class AnimalTest01{
//方法传递的参数是引用数据类型Animal
public static void m1(Animal a){
int age = a.getAge();
a.setAge(++age);
System.out.println("m1中的age=" + a.getAge());
}
public static void main(String[] args){
//1.创建Animal对象
Animal a = new Animal();
a.setAge(10);
m1(a);
System.out.println("main中的age" + a.getAge());
}
}
- 上面打印的结果:
m1中的age=11
main中的age=11
两个打印的结果都是11,与基本数据类型的结果是不一样的,这是因为方法参数传过去的是对象的内存地址,m1方法里面的a和main方法里面的a所指向的是同一个Animal对象,所以当m1方法中修改了Animal对象中的age之后,main方法里面在获取age时,值也发生了改变。
构造方法
构造方法的概念
构造方法(constructor),有的地方叫做构造器或者构造函数。
构造方法的作用是给对象数据进行初始化。
构造方法格式特点
- 方法名与类名相同(注意大小写也要与类名一致)
- 没有返回值类型
- 没有void修饰
- 没有具体的返回值return;
- 如果一个类没有提供任何构造方法,系统默认提供无参数构造方法
- 如果一个类已经手动的提供了构造方法,那么系统不会再提供任何构造方法。
无参构造方法的使用
- 请看下面示例,定义一个User类:
public class User{
private String name;
private int age;
//定义无参构造方法
User(){
System.out.println("User的无参数构造方法执行!");
}
public String getName(){
return name;
}
public void setName(String _name){
name = _name;
}
public int getAge(){
return age;
}
public void setAge(int _age){
age = _age;
}
}
- 创建一个UserTest01测试类:
public class UserTest01{
public static void main(String[] args){
User u = new User();
//u.User();不能手动调用构造方法
}
}
小提示:上面代码在创建User对象的时候,就打印出了构造方法里面的语句,说明在创建对象的时候会默认执行无参构造方法。构造方法不能手动调用。
有参构造方法的使用
构造方法是可以构成重载的,可以写个有参数的构造方法为对象进行数据初始化
public class User{
private String name;
private int age;
//定义无参构造方法
User(){
System.out.println("User的无参数构造方法执行!");
}
//定义有参构造方法
User(String _name,int _age){
name = _name;
age = _age;
}
public String getName(){
return name;
}
public void setName(String _name){
name = _name;
}
public int getAge(){
return age;
}
public void setAge(int _age){
age = _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());
}
}
上面的代码演示了如何使用构造方法给对象数据进行初始化。
接下来再看一个问题,将上面User方法里面的无参构造方法去掉
public class User{
private String name;
private int age;
//定义无参构造方法
/*去掉无参的构造方法
User(){
System.out.println("User的无参数构造方法执行!");
}
*/
//定义有参构造方法
User(String _name,int _age){
name = _name;
age = _age;
}
public String getName(){
return name;
}
public void setName(String _name){
name = _name;
}
public int getAge(){
return age;
}
public void setAge(int _age){
age = _age;
}
}
在测试类里面创建User对象
public class UserTest01{
public static void main(String[] args){
User u = new User();
}
}
在编译时上面代码将会报错,这是因为我们已经手动的编写了一个带有参数的构造方法,那么系统将不会为我们提供默认的无参构造方法了。上面代码在创建对象时会调用无参的构造方法,此时在User类中没有无参构造方法,所以报错。
小提示:【注意】如果我们还想使用无参构造方法,就必须自己写出。建议自己写出无参构造方法
this关键字
this是什么?
this是java里面的一个关键字,是一种引用类型,在堆(heap)中的每个java对象上都有一个this指向自己。this代表着当前对象的引用。
this可以做什么?
- 1.可以区分成员变量和局部变量。
/*
创建一个歌手类
*/
class Singer{
private String name;
/*
以前在编写set方法时为了将传入参数的名字和成员变量的名字做区分
这两个变量的名字是不一样的
public void setName(String _name){
name = _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");
}
}
2.可以调用构造方法
语法: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()+"日");
}
}
- 再谈局部变量和成员变量
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 = "周润发";
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;
}
}
小提示:在Java里面,局部变量和成员变量的标识符是可以相同的,如果有相同的标识符,则可以通过this调用成员变量
小练习
- 写一个长方形类,里面提供计算周长和面积的方法
/*
成员变量:
宽width,高high
成员方法:
setXxx和getXxx
求周长:getLength()
求面积:getArea()
*/
class Rectangle {
//宽
private int width;
//高
private int high;
public Rectangle(){} //空参构造
public Rectangle(int width,int high) {
this.width = width; //有参构造
this.high = high;
}
public void setWidth(int width) {
this.width = width;
}
public int getWidth() {
return width;
}
public void setHigh(int high) {
this.high = high;
}
public int getHigh() {
return high;
}
//计算周长
public int getLength() {
return 2 * (width + high);
}
//计算面积
public int getArea() {
return width * high;
}
}
public class Rectangle1Test {
public static void main(String[] args) {
Rectangle1 r = new Rectangle1(10,20);
System.out.println(r.getLength());
System.out.println(r.getArea());
}
}
static关键字
static的作用?
- static可以修饰变量,被static修饰的变量叫做静态变量,静态变量在类加载阶段赋值,并且只赋值一次。
public class Demo1 {
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;
}
}
- static可以修饰方法,被static修饰的方法叫做静态方法,不用创建对象就能能直接访问该方法,即使用类名.静态方法名的方式。静态方法不能访问非静态的数据,静态方法不能使用this。
class Demo2{
//成员变量
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(); //不会报出空指针异常
}
}
- static可以定义静态语句块,静态语句块在类加载阶段执行,并且只执行一次,并且是自上而下的顺序执行,在构造方法之前执行。
public class Demo3{
//静态语句块
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();//静态语句块只执行了一次
}
}
- 请问下面代码是否可以正常执行?
public class StaticTest04{
static{
System.out.println(i);
}
static int i = 100;
}
小提示:上面代码编译报错,因为静态代码块的执行顺序是自上而下,静态代码块里面打印的变量i还没有声明。
static修饰的变量、方法、代码块都是隶属于类(class)级别的,跟对象无关。某一类物体如果可以被多个其他物体所共享,那么可以将这类物体使用static修饰。
比如wifi,多个人可以共同使用同一个wifi,所以wifi可以使用static修饰。手机是每人使用自己的,就不能用static修饰。
代码块的分类
什么是代码块?
使用{}括起来的代码被称为代码块,根据其位置和声明的不同可以分为下面4种:
- 局部代码块,在方法中出现,限定变量生命周期,及早释放,提高内存利用率
- 构造代码块,在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行
- 静态代码块, 在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且只执行一次。一般用于加载驱动。
- 同步代码块(后面多线程部分会讲解)
public class Demo1 {
public static void main(String[] args) {
//局部代码块
{
int x = 10; //限定变量的声明周期
System.out.println(x);
}
Student s1 = new Student();
System.out.println("---------------");
Student s2 = new Student();
}
static {
System.out.println("main方法类中的静态代码块");
}
}
class Student {
public Student(){
System.out.println("构造方法");
}
//构造代码块:每创建一次对象就会执行一次,优先于构造方法执行
{
System.out.println("构造代码块");
}
static { //随着类加载而加载,且只执行一次
System.out.println("静态代码块"); //作用:用来给类进行初始化,一般用来加载驱动
} //静态代码块是优先于主方法执行
}
方法执行顺序:
- 1.静态代码块,随着类加载而加载,且只执行一次
- 2.构造代码块,每创建一个对象就会执行一次,优先于构造方法执行
- 3.构造方法,每创建一个对象就会执行一次
练习
- 请问下面代码的运行结果是什么?
class Teacher {
static {
System.out.println("Teacher 静态代码块");
}
{
System.out.println("Teacher 构造代码块");
}
public Teacher() {
System.out.println("Teacher 构造方法");
}
}
public class TeacherTest {
static {
System.out.println("TeacherTest静态代码块");
}
public static void main(String[] args) {
System.out.println("main方法");
Teacher t1 = new Teacher();
Teacher t2 = new Teacher();
}
}
- 输出:
TeacherTest静态代码块
main方法
Teacher 静态代码块
Teacher 构造代码块
Teacher 构造方法
Teacher 构造代码块
Teacher 构造方法
继承extends
什么是继承?
继承是面向对象三大特征之一。java中的继承描述的是两个类之间的关系,被继承的类称为父类,继承的类称为子类,使用extends关键字来表示。在java语言里面只支持单继承,即一个类只能有一个父类,子类可以继承父类中的非private修饰的成员方法和成员变量,构造方法不能被继承,java里面的继承跟现实生活中的继承颇为相似,现实生活中一个儿子只能有一个父亲,儿子可以继承父亲的房子车子但是不能继承父亲大脑里面的思想和知识。如果一个类没有显示的继承其他类,那么这个类会默认继承Object类,Object是SUN公司提供的java中的根类。
继承的优点
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,是多态的前提
继承的缺点
- 增强了类之间的耦合。
- 软件开发的一个原则是高内聚,低耦合。
- 内聚是一个模块内各个元素彼此结合的紧密程度
- 耦合是一个软件里面不同模块之间相互连接的数量
- 如何使用extends来实现继承关系?
语法:
[修饰符列表] class 子类名 extends 父类名{
类体;
}
- 首先,来自己定义一个父类SuperClass
public class SuperClass{
public void m1(){
System.out.println("SuperClass中的m1方法");
}
private void m2(){
System.out.println("SuperClass类中的m2方法");
}
}
- 然后定义一个子类SubClass来继承SuperClass
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();
}
}
- 将上面代码修改一下,创建一个SuperClass的父类SuperSuperClass
public class SuperSuperClass{
public void m0(){
System.out.println("SuperSuperClass中的m0方法");
}
}
- 让SuperClass继承SuperSuperClass
public class SuperClass extends SuperSuperClass{
public void m1(){
System.out.println("SuperClass中的m1方法");
}
public void m2(){
System.out.println("SuperClass类中的m2方法");
}
}
写一个测试类来测试一下
public class Test02{
public static void main(String[] args){
SubClass s = new SubClass();
s.m0();//可以调用
}
}
小提示:Java不支持多继承,但是支持多层/重继承,即子类—>父类—>爷爷类….—>祖先类—>Object类,子类可以访问其先辈类里面的非private修饰的成员方法和成员变量。
方法的重写(override)
什么是重写?
重写,也叫做覆盖,当父类中的方法无法满足子类需求时,子类可以将父类的方法进行重写编写来满足需求。比如孩子继承了父亲的房子,可以将房子重新装修。
方法重写的条件:
- 两个类必须是继承关系
- 必须具有相同的方法名,相同的返回值类型,相同的参数列表.
- 重写的方法不能比被重写的方法拥有更低的访问权限。
- 重写的方法不能比被重写的方法抛出更宽泛的异常。(关于异常后面的章节再讲。
- 私有的方法不能被重写。
- 构造方法无法被重写,因为构造方法无法被继承。
- 静态的方法不存在重写。
- 重写指的是成员方法,和成员变量无关。
先定义一个动物类
class Animal {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
定义一个Cat类继承Animal
class Cat extends Animal {
public void eat() {
System.out.println("吃猫粮");//猫喜欢吃猫粮
}
}
定义一个Dog类继承Animal
class Dog extends Animal {
public void eat() {
System.out.println("吃狗粮");//狗喜欢吃狗粮
}
}
Animal测试类
public class AnimalTest01 {
public static void main(String[] args) {
Cat c = new Cat();
c.eat();
Dog d = new Dog();
d.eat();
}
}
小提示:上代码中的Cat类和Dog类在调用父类Animal中的eat方法时已经不能很好的满足自身的需求了,所以都将eat类进行了重写。
静态的方法不存在重写
public class Test01{
public static void main(String[] args){
A.m1();
B.m1();
}
}
class A{
//静态方法
public static void m1(){
System.out.println("A中静态方法m1");
}
}
class B extends A{
//尝试重写父类的静态方法
public static void m1(){
System.out.println("B中静态方法m1");
}
}
super关键字
什么是super?
super代表的是当前子类对象中的父类型特征。
什么时候使用super?
- 子类和父类中都有某个数据,例如,子类和父类中都有name这个属性。如果要再子类中访问父类中的name属性,需要使用super。例1
- 子类重写了父类的某个方法(假设这个方法名叫m1),如果在子类中需要调用父类中的m1方法时,需要使用super。例1
- 子类调用父类中的构造方法时,需要使用super。
注意:super不能用在静态方法中。
先定义一个Animal类
class Animal {
public String name = "动物";
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
定义一个Dog类继承Animal
class Dog extends Animal {
public String name = "旺财";
public void eat() {
System.out.println("吃狗粮");//狗喜欢吃狗粮
}
public void m1(){
System.out.println(super.name);//调用父类中的name变量
System.out.println(this.name);//可以不加this,系统默认调用子类自己的name
super.eat();//调用父类中的eat方法
this.eat();
//eat();
}
}
测试类
public class AnimalTest01 {
public static void main(String[] args) {
Dog d = new Dog();
d.m1();
}
}
class Animal {
//颜色
String color;
//品种
String category;
public Animal(){
System.out.println("Animal中的构造方法");
}
public Animal(String color,String category){
this.color = color;
this.category = category;
}
}
class Dog extends Animal {
public Dog(){
super("土豪金","藏獒");//手动调用父类中的有参构造方法给成员变量进行赋值
System.out.println("Dog中的构造方法");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
System.out.println(d.color);
System.out.println(d.category);
}
}
注意:一个构造方法第一行如果没有this(…);也没有显示的去调用super(…);系统会默认调用super();如果已经有this了,那么就不会调用super了,super(…);的调用只能放在构造方法的第一行,只是调用了父类中的构造方法,但是并不会创建父类的对象。
super和this的对比
- this和super分别代表什么
- this:代表当前对象的引用
- super:代表的是当前子类对象中的父类型特征
- this和super的使用区别
- 调用成员变量
- this.成员变量: 调用本类的成员变量
- super.成员变量: 调用父类的成员变量
- 调用构造方法
- this(…) :调用本类的构造方法
- super(…):调用父类的构造方法
- 调用成员方法
- this.成员方法:调用本类的成员方法
- super.成员方法:调用父类的成员方法
- 调用成员变量
继承相关面试题
请问下面程序的输出结果是什么
class Fu{
public int num = 125;
public Fu(){
System.out.println("fu");
}
}
class Zi extends Fu{
public int num = 256;
public Zi(){
System.out.println("zi");
}
public void show(){
int num = 512;
System.out.println(num);
System.out.println(this.num);
System.out.println(super.num);
}
}
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
答案:
fu
zi
512
256
125
分析:
创建Zi的对象之后系统会先调用Zi的父类Fu中的构造方法,所以先打印了fu
之后系统调用Zi中的构造方法,打印出zi
在show方法里面有个局部变量num=512,第一行打印num,调用的是局部变量,结果是512
通过this调用了成员变量num,打印出256
通过super调用父类中的num,打印出125
请问下面程序输出的结果是什么?
public class Test2_Extends {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
答案:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
分析:
- 1.系统将Fu.class和Zi.class分别加载到方法区的内存里面,静态代码会随着.class文件一块加载到方法区里面,所以先打印出了静态代码块中的内容。
- 2.构造代码块优先于构造方法执行,父类初始化之前,所以打印出父类中的构造代码块和构造方法中的内容。
多态
多态简介
多态就是事物存在的多种形态,比如你在大街上看见一只藏獒,你可以说这只藏獒真凶猛,也可以说这只狗真凶猛,还可以说这个动物真凶猛,以上三种说法其实都是指的这只藏獒。
只要全部符合下面这三种情况,就是多态
- 有继承
- 有方法重写
- 有父类引用指向子类对象
定义一个Animal类
public class Animal{
int num = 10;
public void eat(){
System.out.println("动物在吃!");
}
}
定义一个Cat类继承Animal
public class Cat extends Animal{
int num = 20;
//重写
public void eat(){
System.out.println("猫吃猫粮");
}
//Cat特有的方法.
public void move(){
System.out.println("猫走路很轻盈!");
}
}
定义一个Dog类继承Animal
public class Dog extends Animal{
//重写
public void eat(){
System.out.println("狗啃骨头!");
}
}
上面的三个类里面已经有继承和方法重写了,那么父类引用指向子类对象是什么?请看下面这段代码:
public class AnimalTest01{
public static void main(String[] args){
Cat c1 = new Cat();
c1.eat();
Animal a1 = new Cat();//父类引用指向子类对象
a1.eat();
System.out.println(a1.num);//因为成员变量不存在重写,所以结果是10
}
}
静态绑定和动态绑定
上面代码中,a1是Animal类型的一个引用,指向的是其子类Cat的对象,这个就叫做父类引用指向子类对象。程序在编译的时候a1被看做Animal类型,所以a1.eat()绑定的是Animal类中的eat()方法,这叫做静态绑定,程序运行时,a1指向的是堆中的Cat对象,而在Cat中对eat()方法进行了重写,所以在运行阶段绑定的是Cat中的eat()方法,这叫做动态绑定。
强制类型转换
上面代码中子类向父类型进行转换,是自动类型转换,也叫做向上转型。还有一种情况是父类向子类型转换,是强制类型转换,也叫向下转型。下面的代码演示了强制类型转换
public class AnimalTest01{
public static void main(String[] args){
Animal a1 = new Cat();//父类引用指向子类对象
//如果要是想执行Cat里面的move方法该怎么办?
//只能强制类型转换,需要加强制类型转换符
Cat c1 = (Cat)a1;
c1.move();
Animal a2 = new Dog(); //向上转型.
//强制类型转换
//Cat c2 = (Cat)a2; //会报错 java.lang.ClassCastException
}
}
instanceof关键字【了解】
上面的代码里面将一个指向Dog对象的Animal引用a2进行强制转换成Cat类型时报出了ClassCastException类转型错误,开发中要是想避免这种错误需要使用instanceof来判断一下。
public class AnimalTest01{
public static void main(String[] args){
Animal a1 = new Cat();//父类引用指向子类对象
//如果要是想执行Cat里面的move方法该怎么办?
//只能强制类型转换,需要加强制类型转换符
Cat c1 = (Cat)a1;
c1.move();
Animal a2 = new Dog(); //向上转型.
//进行强制类型转换时,需要先使用instanceof进行判断,避免ClassCastException
if(a2 instanceof Cat){
//强制类型转换
Cat c2 = (Cat)a2;
}
}
}
多态的优点
- 提高了程序的扩展性
- 降低了代码之间的耦合
新建一个Car类
class Car{
public void run(){
System.out.println("汽车在跑");
}
}
创建一个Benz类继承Car
class Benz extends Car{
public void run(){
System.out.println("奔驰汽车在跑");
}
}
创建一个BMW类继承Car
class BMW extends Car {
public void run(){
System.out.println("宝马汽车在跑");
}
}
创建一个Person类用来开车
class Person {
/*
public void drive(Benz bc){
bc.run();
}
奔驰汽车坏了,再重新创建一个开宝马汽车的方法
public void drive(BMW bm){
bm.run();
}
*/
//上面代码扩展性太差,每新增加一种品牌的汽车就需要再写一个方法
//将参数修改为Car类型,这样不论增加什么样的品牌汽车,都可以调用这个方法
public void drive(Car c){
c.run();
}
}
创建一个测试类
public class Test01 {
public static void main(String[] args) {
Person james = new Person();
Benz bc = new Benz();
james.drive(bc);
BMW bm = new BMW();
james.drive(bm);
}
}
小提示:在工作当中尽量面向抽象编程,不要面向具体编程。
final关键字
final的特点
final的中文意思是最终,既然是最终就是已经结束了,无法再改变了。在Java里面final关键字同样也有着类似的功能。
- final修饰的类无法被继承。
final class A{}
class B extends A{}//error无法继承
- final修饰的方法无法被重写。
class A{
public final void m1(){}
}
class B extends A{
public void m1(){}//error无法重写
}
- final修饰的局部变量,一旦赋值,不可再改变。
class A{
public void m1(){
//声明
final int i;
//第一次赋值
i = 100;
//error不能重新赋值
i = 1200;
}
}
- final修饰的成员变量必须初始化值。
class A{
//final修饰的成员变量必须手动初始化.
final int i = 100;
//error必须进行初始化
final int k;
//final修饰的成员变量一般和static联用。
java规范中要求所有的常量"大写"
public static final double PI = 3.14;
}
final修饰引用类型
final修饰的引用类型,该引用不可再重新指向其他的java对象。但是fianl修饰的引用,该引用指向的对象的属性值是可以修改的。
- 基本类型,是值不能被改变
- 引用类型,是地址值不能被改变,对象中的属性可以改变
public class FinalTest01{
public static void main(String[] args){
final Customer c = new Customer("张三",20);
//c是final的,无法重新赋值。
//c = new Customer("李四",21);//Error
c.name = "王五";
c.age = 25;
System.out.println(c.name);
System.out.println(c.age);
}
}
class Customer{
String name;
int age;
Customer(String name,int age){
this.name = name;
this.age = age;
}
}
抽象类
抽象的概念
抽象这个词说白了就是看不懂,毕加索的画一般都是被称为抽象的。在java里面可以使用关键字abstract修饰一个类,这样的类被称为抽象类,abstract修饰的方法叫做抽象方法。抽象类或抽象方法一般也是看不懂的,因为里面可能根本就没有代码。
抽象类的特点
- 抽象类无法被实例化,无法创建抽象类的对象。
- 虽然抽象类没有办法实例化,但是抽象类也有构造方法,该构造方法是给子类创建对象用的。这也算是多态的一种。
- 抽象类中不一定有抽象方法,但抽象方法必须出现在抽象类中。
- 抽象类中的子类可以是抽象类,如果不是抽象类的话必须对抽象类中的抽象方法进行重写。
- 抽象类和抽象方法不能被final修饰
public abstract class A{
//构造方法
A(){
System.out.println("A....");
}
//抽象方法
public abstract void m1();
public static void main(String[] args){
//抽象类无法创建对象.
//A a = new A();
//多态
A a = new B();
}
}
class B extends A{
public void m1(){
}
B(){
super(); //父类的构造方法虽然调用了,但是并没有创建父类对象。
System.out.println("B....");
}
}
接口
接口的概述
电脑上面的主板有很多接口,比如内存条的接口,有了这个接口,可以插入多个内存条,主板和内存条可能不是同一家生产厂商,但是两种物体却能结合到一起,正是因为这个接口的存在。只要厂家遵循这个接口,主板和内存条就可以随意更换,提高了可插拔性,接口其实也是体现着一种规范。
在java语言里面使用interface来声明一个接口,接口其实是一个特殊的抽象类,在接口里面的方法全部都是抽象的。
关于接口,有几个需要注意的地方:
- 接口中只能出现常量和抽象方法(jdk8之后可以有default方法)
- 接口里面没有构造方法,无法创建接口的对象
- 接口和接口之间支持多继承,即一个接口可以有多个父接口
- 一个类可以实现多个接口,即一个类可以有多个父接口
- 一个类如果实现了接口,那么这个类需要重写接口中所有的抽象方法(建议),如果不重写则这个类需要声明为抽象类(不建议)
public interface A{
//常量(必须用public static final修饰)
public static final double PI = 3.14;
//public static final是可以省略的.
//double PI = 3.14;
//抽象方法(接口中所有的抽象方法都是public abstract)
public abstract void m1();
//public abstract是可以省略的.
void m2();
}
interface B{
void m1();
}
interface C{
void m2();
}
interface D{
void m3();
}
interface E extends B,C,D{
void m4();
}
//implements是实现的意思,是一个关键字.
//implements和extends意义相同。
class MyClass implements B,C{
public void m1(){}
public void m2(){}
}
接口的作用
- 可以使项目分层,都面向接口开发,提高开发效率
- 降低了代码之间的耦合度,提高了代码的可插拔性
小提示:开发中尽量使用接口,少用抽象类,一个类可以实现多个接口,却只能继承一个父类
将之前的james开汽车的例子修改一下
将Car定义为接口
interface Car {
public void run();
}
创建Benz和BMW类去实现这个接口
class Benz implements Car {
public void run(){
System.out.println("奔驰汽车在跑");
}
}
class BMW implements Car {
public void run(){
System.out.println("宝马汽车在跑");
}
}
Person类不变
class Person {
public void drive(Car c){
c.run();
}
}
测试类不变
public class Test01 {
public static void main(String[] args) {
Person james = new Person();
Benz bc = new Benz();
//james.drive(bc);
//BMW bm = new BMW();
james.drive(bc);
}
}
访问控制权限
4种访问控制权限
java访问级别修饰符主要包括:private 、protected、public和default(默认就是什么都不写),可以限定其他类对该类、属性和方法的使用权限。
修饰符 | 本类 | 同包 | 子类 | 其他(任何地方) |
---|---|---|---|---|
private | 能 | 不能 | 不能 | 不能 |
default(默认) | 能 | 能 | 不能 | 不能 |
protected | 能 | 能 | 能 | 不能 |
public | 能 | 能 | 能 | 能 |
注意以上对类的修饰只有:public和default,内部类除外
priavte和public都比较好理解和记忆,这里就不演示了,主要演示一下不同包下的两个具有父子关系的类里面使用protected和default的区别。
创建一个Person类
package com.cssve.score.sys;
public class Person{
String name;
protected int age;
void m1(){
System.out.println("m1");
}
protected void m2(){
System.out.println("m2");
}
}
创建一个User类,与Person类不在同一个包下
package com.cssve.score.buss;
import com.cssve.score.sys.Person;
public class User extends Person{
public void m3(){
m1();//无法访问,因为父类里面是default修饰的
m2();
System.out.println(age);
System.out.println(name);//无法访问,因为父类里面是default修饰的
}
}