Java入门第三周
前言
从零开始学Java的第三周
一、继续面向对象
1. 什么是继承?
将多个类的共性内容抽取到一个独立的类中,然后多个类和这个独立的类产生一种关系:
这就是继承关系!
关键词:extends
书写格式:
class Fu{}
class zi extends Fu{}
1.1 继承的好处
I.提高代码的维护性
II.提高代码的复用性
III.让类与类之间产生联系,这是多态的前提
在不使用继承前,如果多个类需要用到同样的方法就要在各个类中重复定义同样的的方法,代码的冗余度,大幅提高,使用继承减少冗余.
//没有使用继承之前
学生类
class Student{
定义成员方法
public void eat(){
System.out.println("学习饿了,就需要吃饭...");
}
public void sleep(){
System.out.println("学习累了,就需要休息...");
}
}
//工人类
class Worker{
//定义成员方法
public void eat(){
System.out.println("敲代码敲了,就需要吃饭...");
}
public void sleep(){
System.out.println("敲代码敲困了,就需要休息...");
}
}
//使用继承的写法:
//将Student类和Worker类的共性内容抽取到:Person类 人类
class Person{
public void eat(){
System.out.println("饿了,就需要吃饭...");
}
public void sleep(){
System.out.println("困了,就需要休息...");
}
}
//将学生类和工人类:继承自Person
class Student extends Person{}
class Worker extends Person{}
//测试类
public class ExtendsDemo {
public static void main(String[] args) {
//测试
Student s = new Student() ;
s.eat() ;
s.sleep();
System.out.println("----------------------") ;
Worker worker = new Worker();
worker.eat();
worker.sleep();
System.out.println("=========================================");
//加入的继承
Student s2 = new Student() ;
s2.eat();
s2.sleep();
Worker w2 = new Worker() ;
w2.eat();
w2.sleep();
}
}
根据上面的例子我们可以很容易看出,继承的好处
1.2 继承的特点
I.Java只支持单继承 class Zi extends Fu
II.但是Java可以多层继承
clssa Fu extends Grd{}
class Zi extends Fu{}
//-------------------------------------------------------------------------
class GrandFather{
public void method(){
System.out.println("我是爷爷...");
}
}
class Father extends GrandFather{
/* public void method(){
System.out.println("我是爷爷...");
}*/
public void show(){
System.out.println("shou Father...");
}
}
//单继承
class Son extends Father{
public void function(){
System.out.println("function son...");
}
}
//多继承:类与类之间不支持
//class Son extends Father ,Mother{
//}
//测试类
public class ExtendsDemo2 {
public static void main(String[] args) {
//创建子类对象
Son son = new Son() ;
son.function();
son.show();
son.method();
*
*我们可以看到son既可以使用自己的方法,也可以使用父亲的方法和爷爷的方法.
*
}
}
1.3 继承的注意事项
* 1)子类继承父类:可以继承父类的非私有的成员,
* 私有的成员外界不能访问的,只能在本类中访问
* 但是可以通过公共方法间接访问!
*
* 2)构造方法是不能被继承的,但是子类可以间接通过
* super关键字访问父类的构造方法
class Fu{
//非私有成员变量
public int num = 20 ;
private int num2 = 50 ;
public void show(){
System.out.println(num);
System.out.println(num2);
System.out.println("-----------------");
String str = getStr();
System.out.println(str);
}
//返回String内容
private String getStr(){
return "hello,extends !" ;
}
}
//定义子类
class Zi extends Fu{
//子类自己的方法
public void method(){
System.out.println("method zi ...");
}
}
//测试类
public class ExtendsDemo3 {
public static void main(String[] args) {
//创建子类对象
Zi zi = new Zi() ;
System.out.println(zi.num) ;
// System.out.println(zi.num2) ; //私有的成员变量
System.out.println("----------------------");
//访问show方法,show方法公共的,可以被子类继承
zi.show() ;
zi.method(); //访问自己本类的功能
//zi.getStr() ;//父类中私有的成员方法
//调用不了
//只能间接通过公共来调用
zi.show() ;
}
}
1.4 继承中成员变量(成员方法)的关系
a)子类继承父类,如果子类中的成员变量名称和父类的成员变量名称不一致,分别访问即可!
b)子类继承父类,如果子类的成员变量名称和父类的成员变量名称一致:如何访问呢? (重点)
1)首先在子类的局部位置找,是否存在局部变量名称,如果有,就使用
2)如果没有,就在子类的成员位置找,是否存在这个变量,如果存在,就使用
3)如果在子类的成员位置中没有找到,直接在父类的成员位置中找,如果有,就是使用!
4)如果父类的成员位置都没有,就没有这个变量,报错!
遵循一个原则:就近原则!
成员方法的调用与对象一致
//---------------------------------------------------------------
1.5 继承中构造方法的访问
* 继承中构造方法的访问:
*
* 1)子类继承父类,子类的所有的构造方法都会默认的访问父类的无参方法
*
* 子类的所有构造方法的第一句话:默认隐藏了super() ; 因为子类中肯能会使用到父类的数
* 所以在继承关系中得先让父类初始化---->构造方法 : 分层初始化!(先父类无参构造方法,在
* 执行子类的构造方法)
*
* super:代表的父类对象的空间表示(父类对象的地址值引用!)
*
*
* 2)如果父类中的无参构造方法没有,子类会怎么样?
* 子类的所有的构造都会报错! (因为子类所有构造方法默认父类的无参构造方法!)
*
*
*如何解决呢?
* 方式1:手动给出父类的无参构造方法(推荐)
* 方式2:在子类的构造方法中的第一句话:通过super(xxx),间接的访问父类的有参构造方法
* 方式3:只要子类的所有构造方法中一个能够让父类初始化即可!
* 在子类的有参构造方法中:this():---->访问本类的无参构造方法,然后再子类的无参构造方法中
* 间接访问父类的有参构造方法super(xxx) ;
*
1.6super和this的区别
this:代表的当前类对象的地址值引用
super:代表的父类对象的地址值引用(代表父类的空间标识)
访问成员变量
this.变量名; 访问的本类中的成员变量
super.变量名; 访问的是父类的成员变量
访问构造方法:
this() ; 访问本类的无参构造方法
super() ;访问的父类的无参构造方法
this(xxx);访问的本类的有参构造方法
super(xxx);访问的父类的有参构造方法
成员方法:
this.方法名();访问的是本类的成员方法
super.方法名() ;访问的是父类的成员方法
1.7 初始化以及代码块优先级问题
* 1)继承的初始化问题:
* 分层初始化:先让父类初始化,然后子类初始化
* 2)代码块的优先级
* 跟静态相关的先执行:静态代码块先执行,就执行一次!!!
* 随着类加载而加载,仅执行一次
* 静态代码块 > 构造代码块 > 构造方法
1.7.1 代码块分类
代码块
*
* 在Java中,使用{}包裹棋起来的内容,成为代码块!
*
* 分类:
* 局部代码块 :在方法定义中使用 ,作用:限定局部变量的生命周期
* 构造代码块:在类的成员位置(类中,方法外),使用{}包裹起来 作用:给类中的一些成员进行数据初始化
* 特点:每次在执行构造方法之前,如果存在构造代码块,先执行构造代码块中的内容!
*
* 静态代码块:
* 在类的成员位置,直接使用 static{},特点:随着类的加载而加载,优先于对象存在!
class Code {
//静态代码块
static{
int x = 1000;
System.out.println(x);
}
//无参构造方法
public Code(){
System.out.println("code demo...");
}
//构造代码块
{
int x = 100 ;
System.out.println(x);
}
{
int y = 200 ;
System.out.println(y);
}
//有参构造方法
public Code(String name){
System.out.println("code demo"+name);
}
//静态代码块
static{
int y = 2000 ;
System.out.println(y);
}
}
//测试类
public class CodeDemo {
public static void main(String[] args) {
//定义一个变量
// int a = 10 ;
// System.out.println(a);
//定义一个局部代码块
{
int x = 20 ;
System.out.println(x); //x的作用域就是在{}内有效
}
// System.out.println(x);
//创建一个Code类对象
Code code = new Code() ;
System.out.println("-----------");
Code code2 = new Code("hello") ;
}
}
1.8 继承的时机
不要为了部分功能而且继承,可能会吧不需要的功能也继承过来.
继承存在局限性
正确的使用继承关系 应该是A是B类的一种,这时就可以使用继承关系
继承关系的现实世界体现是一种 "is a"的关系
2. 方法重写(多态之前提)
如果子类出现了和父类一模一样的方法声明,叫做方法重写!(Override) —方法复写(覆盖)
格式:
* 权限修饰符 返回值类型 方法名(参数列表){
* ...
* }
* tips:权限修饰符等级不能比父类方法更低!!!!!!!!!!!
2.1 方法重写与方法重载的区别
方法重写和方法重载的区别?
*
* 方法重载: 在一个类中,提供n多个功能,这些功能,方法名相同,参数列表不同,与返回值无关(目的:提高某个功能的扩展性)
* 参数列表不同:
* 1)类型不同
* 2)个数不同
* 3)考虑参数类型的顺序
*
* public static void open(int a,double d){}
* public static void open(double a,int b){}
* 构造方法也可以重载!(同类中)
*
public Demo(){}
public Demo(int a){}
*
* 方法重写: 在继承关系中,子类出现了父类一模一样的方法声明,重写的目的:子类有自己的功能,需要将父类的该功能覆盖掉!
*
3. 多态
一个事物的不同形态
对象在内存中的变化
3.1 多态的前提条件
* 1)必须存在继承关系 (extends)
* 2)必须存在方法重写
* 子类需要覆盖父类的功能
* Animal
* eat():"动物都需要吃饭..."
* Cat
* eat() "猫吃鱼"
* Dog
* eat() "狗吃骨头"
*
* 3)必须有父类引用指向子类对象
* class Fu{}
* class Zi extends Fu{
* //存在重写
* }
*
* 格式: Fu fu = new Zi() ;
*
3.2 多态的好处
1.提高代码的复用性(由继承保证)
2.提高了代码的扩展性
父类引用指向子类
Fu fu = new zi
3.3 多态的成员访问特点
*
* 1)针对成员变量的访问问题:
* 编译看左,运行看左!(使用父亲的东西)
* 2)针对多态中的成员方法的访问问题: 我们所说的成员变量/成员方法----非static
* 编译看左,运行看右(因为子类重写了父类的东西)
* 3)如果成员方法是静态方法:(静态成员方法 算不上方法重写,直接可以类名来访问,跟类相关的方法)
* 编译看左,运行看左
* 4)构造方法:
* 存在继承关系: 还需要让父类先初始化,然后再子类进行数据初始化!(分层初始化!)
3.4 多态的弊端
不能访问子类的特有功能
Fu fu = new zi
fu.方法名 报错 fu中无此方法
那么如何解决弊端?
使用向下转型
把父类引用指向子类,转换为子类引用即可
Fu fu = new zi
Zi zi = (Zi) fu
zi.方法名()即可调用子类的特有功能
且不需在堆中从新开辟空间
4. 接口
接口现实世界事物的扩展功能
谁接入接口就拥有此功能
接口的定义格式
interface 接口名 {}
4.1 接口的特点
1.接口中无构造方法
2.接口中的成员默认常量
3.接口中成员方法只能是抽象方法(无方法体)
接口也无法实例化,只能通过具体只实现类实例化-----接口多态
4.2 接口,类之间的关系
类与类之间 继承关系 extends
只支持单继承 但是可以多层继承
类与接口之间 实现关系 implements
类可以在继承的同时实现多个接口,接口之间用逗号隔开
接口与接口之间 继承关系 extends
不仅支持单继承,还可以多继承 之间用逗号隔开
4.3 instance of
属于一种二目运算符号
用来比较左边的对象是否位右边对象的一个实例
abstract class Animal{
public Animal() {
}
public abstract void eat() ;
}
class Dog extends Animal{
public Dog() {
}
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class Pig extends Animal{
@Override
public void eat() {
System.out.println("猪吃白菜...");
}
}
//测试类
public class Test3 {
public static void main(String[] args) {
Animal a = new Dog() ;
a = new Pig() ;
//if语句
if(a instanceof Dog){
System.out.println("是狗类型的实例");
}else if(a instanceof Cat){
System.out.println("是猫类型的实例");
}else if(a instanceof Pig){
System.out.println("是猪类型的实例");
}else{
System.out.println("对不起,没有当前实例...");
}
}
}
二、一些常用关键字
1.static(静态)
如果有一个大家都相同的属性,那么重复定义太消耗内存
Java提供关键字:static: 最基本概念:共享,共用
给变量加上static
且static修饰的变量属于类变量,优先于成员变量加载,在类加载时已经进内存了
**
* 静态static关键字的特点:
*
* 1)随着类的加载而加载
*
* 2)优先于对象存在: 它不能this共存 (this:代表当期类对象的地址值引用)
* 对象还没有new的时候,当前被static修饰的成员就已经内存了
*
* 3)被静态修饰的 可以被多个对象共享:有共享共用的意思
*
* 举例:
* 饮水机中的水(适合)
* 水杯共享(不适合!)
*
* 4)被静态修饰的变量,方法----->静态变量或者静态方法
* 我们所说的成员变量和成员方法:都指的是非静态
*
* 静态的成员的访问方式:类名.变量
*
* 类名.方法名()
*
*/
Tips:* 关于static关键字的使用注意事项
1)非静态的方法既可以访问静态变量,也可以访问非静态的变量
既可以调用静态方法,也可以调用非静态方法
2)静态的方法:只能访问静态变量,
只能调用静态方法
2.final
* 关于final关键字的特点:
* 1)可以修饰类,该类不能被继承!
* 2)可以修饰符成员方法,成员方法不能重写! (根据具体的题意要求!)
* 3)可以修饰的变量,这个变量此时是一个常量! (自定义常量)
*
* final修饰基本数据类型和引用类型的区别?
*
* final修饰基本数据类型: 基本数据类型的对应的数据值不能在被赋值了,只能赋值一次!
* final修饰引用类型:引用数据类型对应的地址值不能被改变
* final Student s = new Student() ;//修饰实例变量s,s的堆内存地址值永远是固定值!
* s = new Student() ;//重新开辟空间(报错)
*
*
3.abstract(抽象)
abstract可用来修饰方法和类
变为抽象方法和抽象类
abstract class{}
abstract void xxx();
3.1 抽象类的特点
1.有抽象方法的类一定是抽象类
2.抽象类中不是只有抽象方法,还可以有非抽象方法
3.抽象类不能实例化(不能直接创建对象)-----抽象类多态
可通过子类间接实例化:父类引用指向子类
4.抽象类的子类可以是抽象类或者具体子类
但是如果子类是抽象类就无意义,除非还有子类是具体子类
5.抽象类有构造方法,为了分层初始化
抽象类的核心宗旨:就是强制子类必须完成的事情(要将父类中的所有的抽象方法必须重写,否则报错!)
3.2 与哪些关键字冲突
private冲突:
私有的方法,只能在本类中访问,加入abstract目的需要被子类重写,需要调用子类的功能!
static冲突:
静态方法随着类的加载而加载,而静态方法---算不上重写,所以它的访问
通过类名.方法名()---->而抽象方法没有方法体
final冲突
被final修饰的成员方法是不能被重写的,而加入abstract的方法需要被重写的!
4.权限修饰符
默认修饰符
私有修饰符:private
受保护的:protected
公共的,公开的:public
本类中 同一个包下的子类中/无关类中 不同包的子类中 不同包下的无关类中
private √
默认修饰符 √ √
protected √ √ √
public √ √ √ √
*
* 修饰的权限从小到:private,默认,protected,public
*
三、方法的问题
1.参数是引用数据类型(String除外)
在第二周中有提到方法的形式参数问题,这里加以补充
如果方法的形式参数是类
调用方法的时候,如何传递实际参数?
具体类,调用该方法,实际参数需要传递当前具体类的对象
抽象类,调用该方法实际参数需要传递的抽象类的子类对象 (抽象类多态)
形式参数为接口,调用该方式,实际参数需要传递的是当前接口的子实现类对象(接口多态)
共同点:总是需要传入具体实例,操作方法
2. 方法的返回值问题
如果一个方法的返回值是引用类型,最终方法结束,如何返回?
* 类
* 具体类:方法返回的就是当前具体类对象!
* 抽象类:需要返回的是抽象类的子类对象
*
* 接口:需要返回的是该接口的子实现类对象
*
*方法调用的返回值必须是实现类的对象接受
例如:方法的返回值是一个接口
interface Mary{
public abstract void mary() ;
}
//MaryDemo类
class MaryDemo{
public Mary function(){//返回值类型是接口类型,
// Mary mary = new Mary() ; //接口不能实例化
//需要提供接口的子实现类对象
//接口多态
// Mary mary = new You() ;
// return mary ;
//一步走
return new You() ;
}
}
//定义一个类
class You implements Mary{
@Override
public void mary() {
System.out.println("结婚了,很开心...");
}
}
//测试类
public class LoveTest {
public static void main(String[] args) {
//调用MaryDemo类中的function方法?
MaryDemo maryDemo = new MaryDemo() ;
Mary mary = maryDemo.function();
mary.mary();
System.out.println("----------------------");
//匿名对象
Mary m = new MaryDemo().function();
m.mary();
}
}
四、内部类
在一个类中可以定义另一个类
如 在A类中定义一个B类
那么B就是A的内部类
1.使用内部类有什么好处
-
1)实现多重继承;
-
2)内部类可以很好的实现隐藏:一般的非内部类,是不允许有 private 与protected权限的,但内部类可以
-
3)减少了类文件编译后的产生的字节码文件的大小
2.内部类的种类
成员内部类、静态内部类、局部内部类、匿名内部类
2.1 成员内部类:
在一个类成员位置的另一个类:
class Outer{
//成员变量
public int num = 100 ;
private int num2 = 200 ;
class Inner{ //成员内部类
//成员内部类的一个成员方法
public void method(){
System.out.println("method Inner");
System.out.println();
System.out.println(num2);
}
}
2.2 静态内部类
可以通过.的形式直接访问内部类和他的成员方法
Outer3.Inner3.show2();
class Outer3{
//定义非静态的成员变量
public int num = 50 ;
private static int num2 = 20 ;
//定义成员内部类:静态的 ---->静态的成员内部类可以看成是外部类的静态成员
static class Inner3{//此时类都是静态
public void show(){
// System.out.println(num);
System.out.println(num2);
}
public static void show2(){
// System.out.println(num);
System.out.println(num2);
}
}
}
2.3 局部内部类
在一个方法中定义的类称为局部内部类
注意事项:
- 局部变量的生命周期是随着方法调用而存在,随着方法调用结束而消失而当前外部类对象调用method 方法的时候,此时num进入栈内存,在局部位置创建了局部内部类对象而局部内部类对象调用它的成员方法访问该变量,方法method方法结束之后,内部类对象不会立即消失,它里面的成员方法在访问局部变量,局部变量必须变成常量,常驻内存,否则如果当前变量消失了,局部内部类的成员依然在访问就会出现冲突! 所以 jdk7 收到必须加入final修饰,jdk8通过jvm已经做了优化了,无需手动加入final修饰
class Outer{
public int num = 100 ;
private int num2 = 200 ;
//成员方法
public void method(){
//局部位置:局部内部类
class Inner{
//局部内部类的一个成员方法
public void show(){
System.out.println(num);
System.out.println(num2);
}
}
//创建局部内部类对象
Inner inner = new Inner() ;
inner.show() ;
}
}
2.4 匿名内部类
没有名字的内部类
一般在我们局部位置使用!
格式:
匿名内部类它是内部类的一种简化格式
new 类名(可以是抽象类,也可以具体类)或者是接口名(){
重写功能
} ;
匿名内部类的本质:
继承了该类或者是实现了该接口的子类对象
interface Inter{
void show() ;
void show2() ;
}
class Outer{
public void method()
Inter inter = new Inter(){
@Override
public void show() {
System.out.println("show Inter...");
}
@Override
public void show2() {
System.out.println("show2 Inter...");
}
};
i.show();
i.show2():
}
}
3.内部类的特点
内部类可以访问外部类的成员变量,包括私有!
可看作是同一个类,自然可以访问私有变量
外部类想要访问内部类成员方法需要
前提条件内部类不能被静态修饰
Outer2.Inner2 oi = new Outer2().new Inner2() ;
oi.方法名
如果内部类是静态的那么如何直接访问静态成员内部类的成员呢?
* 将静态的成员内部类看成是外部类的静态成员访问
*
* 直接访问方式
* 外部类名.内部类名 对象名 = new 外部类名.内部类名() ;
class Outer3{
//定义非静态的成员变量
public int num = 50 ;
private static int num2 = 20 ;
//定义成员内部类:静态的 ---->静态的成员内部类可以看成是外部类的静态成员
static class Inner3{//此时类都是静态
public void show(){
// System.out.println(num);
System.out.println(num2);
}
public static void show2(){
// System.out.println(num);
System.out.println(num2);
}
}
}
//测试类
public class InnerClassDemo3 {
public static void main(String[] args) {
// 外部类名.内部类名 对象名 = 外部类对象.内部类对象;
//Outer3.Inner3 oi = new Outer3().new Inner3() ; 适用不了了
// 外部类名.内部类名 对象名 = new 外部类名.内部类名() ;
Outer3.Inner3 oi = new Outer3.Inner3() ;
oi.show();
oi.show2() ; //静态---不推荐对象名访问
System.out.println("------------------------------");
//show2()的另一种方式
Outer3.Inner3.show2(); //show2()静态方法
}
}