类和对象的关系
-
类
-
类的理解
-
类是对现实生活中一类具有共同属性和行为的事物的抽象
-
类是对象的数据类型,类是具有相同属性和行为的一组对象的集合
-
简单理解:类就是对现实事物的一种描述
-
-
类的组成
-
属性:指事物的特征,例如:手机事物(品牌,价格,尺寸)
-
行为:指事物能执行的操作,例如:手机事物(打电话,发短信)
-
-
-
类和对象的关系
-
类:类是对现实生活中一类具有共同属性和行为的事物的抽象
-
对象:是能够看得到摸的着的真实存在的实体
-
简单理解:类是对事物的一种描述,对象则为具体存在的事物
-
类的定义
public class Student {
// 属性 : 姓名, 年龄
// 成员变量: 跟之前定义变量的格式一样, 只不过位置发生了改变, 类中方法外
String name;
int age;
// 行为 : 学习
// 成员方法: 跟之前定义方法的格式一样, 只不过去掉了static关键字.
public void study(){
System.out.println("学习");
}
}
类加载在方法区中,需要的时候调入到栈区中
对象的创建和使用
package com.wedu.object1;
public class TestStudent {
/*
创建对象的格式:
类名 对象名 = new 类名();
调用成员变量的格式:
对象名.变量名
调用成员方法的格式:
对象名.方法名();
*/
public static void main(String[] args) {
// 类名 对象名 = new 类名();
Student stu = new Student();
// 对象名.变量名
// 默认初始化值
System.out.println(stu.name); // null
System.out.println(stu.age); // 0
stu.name = "张三";
stu.age = 23;
System.out.println(stu.name); // 张三
System.out.println(stu.age); // 23
// 对象名.方法名();
stu.study();
// com.wedu.object1.Student@b4c966a
// 全类名(包名 + 类名)
System.out.println(stu);
}
}
把类实例化是一个对象,非静态变量在堆区中,静态变量、静态方法在方法区。
注意事项
构造方法的创建 :
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
构造方法的创建 :
如果没有定义构造方法,系统将给出一个默认的无参数构造方法如果定义了构造方法,系统将不再提供默认的构造方法
推荐的使用方式 :
无论是否使用,都手动书写无参数构造方法,和带参数构造方法
修饰符
访问控制符
-
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private (私人): 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类),类和接口不能申明为private
-
public (公共): 对所有类可见。使用对象:类、接口、变量、方法
-
protected (protected): 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
修饰符 | 当前类 | 同包内 | 同包子类 | 不同包子类 | 其他包 |
public | 1 | 1 | 1 | 1 | 1 |
protected | 1 | 1 | 1 | 1 | 0 |
defult· | 1 | 1 | 1 | 0 | 0 |
private | 1 | 0 | 0 | 0 | 0 |
非访问修饰符
static 修饰符,用来修饰类方法和类变量。
final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
abstract 修饰符,用来创建抽象类和抽象方法。
synchronized 和 volatile 修饰符,主要用于线程的编程。
static修饰符
修饰的变量和方法放到方法区内,只会初始化一次,所以对象共享。
final 修饰符
修饰类
表示类不能被继承
修饰方法
表示该方法不能被子类重写
修饰变量
修饰基本数据类型时表示该数据为一个常量
修饰引用数据类型时表示该类型的地址不会发生改变(即能改变地址中的内容但是不能将其他地址赋值)
abstract 修饰符
abstract
关键字在Java中可以用来修饰两类结构:类(classes)和方法(methods)。修饰之后的特点分别如下:
修饰类特点:
不能实例化:被abstract
修饰的类不能直接创建对象实例,也就是说你不能使用new
关键字来实例化一个抽象类。
必须被继承:抽象类通常作为基类来使用,需要被其他类继承。继承抽象类的子类要么实现所有的抽象方法,要么子类本身也被声明为抽象的。
可以包含抽象方法和非抽象方法:抽象类中可以有抽象方法(只有声明没有实现的方法),也可以有具体实现的方法。
作为接口和实现之间的中间层:抽象类提供了部分实现,为子类提供了一定程度上的通用功能,同时要求子类完成剩余的实现细节
修饰方法特点:
仅有声明没有实现:抽象方法只有方法签名(方法名、参数列表、返回类型),没有方法体(即没有具体的实现代码,以分号;
结束)。
抽象方法不能被声明成 final 和 static
强制子类实现:继承抽象类的子类必须提供抽象方法的具体实现,除非子类也是抽象的。
面向对象的特点
面向对象三大特征(封装,继承,多态)
封装
面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
好处
通过方法来控制成员变量的操作,提高了代码的安全性
把代码用方法进行封装,提高了代码的复用性
继承
继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法。
格式
继承通过extends实现
格式:class 子类 extends 父类 { }
举例:class Dog extends Animal { }
Dog继承Animal类,拥有Animal类的变量(不包括私有变量,私有变量需要通过父类的函数获得)和方法(可以重写)。
继承的优点与缺点
-
继承好处
-
提高了代码的复用性(多个类相同的成员可以放到同一个类中)
-
提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
-
-
继承弊端
-
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
-
-
继承的应用场景:
-
使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承
-
is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类
-
-
继承的特点
只能单继承,不能多继承。
支持多层继承
变量访问特点
在子类方法中访问一个变量,采用的是就近原则。
-
子类局部范围找
-
子类成员范围找
-
父类成员范围找
-
如果都没有就报错(不考虑父亲的父亲…)
方法访问特点
通过子类对象访问一个方法
-
子类成员范围找
-
父类成员范围找
-
如果都没有就报错(不考虑父亲的父亲…)
this与super关键字对比
-
this&super关键字:
-
this:代表本类对象的引用
-
super:代表父类存储空间的标识(可以理解为父类对象引用)
-
-
this和super的使用分别
-
成员变量:
-
this.成员变量 - 访问本类成员变量
-
super.成员变量 - 访问父类成员变量
-
-
成员方法:
-
this.成员方法 - 访问本类成员方法
-
super.成员方法 - 访问父类成员方法
-
-
-
构造方法:
-
this(…) - 访问本类构造方法
-
super(…) - 访问父类构造方法
-
多态
同一个对象,在不同时刻表现出来的不同形态
前提
-
要有继承或实现关系
-
要有方法的重写
-
要有父类引用指向子类对象
示例:
class Animal {
public void eat(){
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test1Polymorphic {
/*
多态的前提:
1. 要有(继承 \ 实现)关系
2. 要有方法重写
3. 要有父类引用, 指向子类对象
*/
public static void main(String[] args) {
// 当前事物, 是一只猫
Cat c = new Cat();
// 当前事物, 是一只动物
Animal a = new Cat();
a.eat();
}
}
成员访问特点
-
成员访问特点
-
成员变量
编译看父类,运行看父类
-
成员方法
编译看父类,运行看子类
-
class Fu {
int num = 10;
public void method(){
System.out.println("Fu.. method");
}
}
class Zi extends Fu {
int num = 20;
public void method(){
System.out.println("Zi.. method");
}
}
public class Test2Polymorpic {
/*
多态的成员访问特点:
成员变量: 编译看左边 (父类), 运行看左边 (父类)
成员方法: 编译看左边 (父类), 运行看右边 (子类)
*/
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);
f.method();
}
}
输出:
10
Zi.. method
好处和弊端
-
好处
提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
-
弊端
不能使用子类的特有成员
多态的转型
-
向上转型
父类引用指向子类对象就是向上转型
-
向下转型
格式:子类型 对象名 = (子类型)父类引用;
示例
class Fu {
public void show(){
System.out.println("Fu..show...");
}
}
class Zi extends Fu {
@Override
public void show() {
System.out.println("Zi..show...");
}
public void method(){
System.out.println("我是子类特有的方法, method");
}
}
public class Test3Polymorpic {
public static void main(String[] args) {
// 1. 向上转型 : 父类引用指向子类对象
Fu f = new Zi();
f.show();
// 多态的弊端: 不能调用子类特有的成员
// f.method();
// A: 直接创建子类对象
// B: 向下转型
// 2. 向下转型 : 从父类类型, 转换回子类类型
Zi z = (Zi) f;
z.method();
}
}
多态中转型存在的风险和解决方案
-
风险
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
-
解决方案
-
关键字
instanceof
-
使用格式
变量名 instanceof 类型
通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
-
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome(){
System.out.println("看家");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test4Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}
public static void useAnimal(Animal a){ // Animal a = new Dog();
// Animal a = new Cat();
a.eat();
//a.watchHome();
// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 类型转换异常
// 判断a变量记录的类型, 是否是Dog
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
构造方法
构造方法的格式和执行时机
-
格式注意 :
-
方法名与类名相同,大小写也要一致
-
没有返回值类型,连void都没有
-
没有具体的返回值(不能由retrun带回结果数据)
-
-
执行时机 :
-
创建对象的时候调用,每创建一次对象,就会执行一次构造方法
-
不能手动调用构造方法
-
作用
-
用于给对象的数据(属性)进行初始化
注意事项
构造方法的创建 :
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
构造方法的创建 :
如果没有定义构造方法,系统将给出一个默认的无参数构造方法如果定义了构造方法,系统将不再提供默认的构造方法
推荐的使用方式 :
无论是否使用,都手动书写无参数构造方法,和带参数构造方法
继承中构造方法的访问特点
注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 子类通过this去调用本类的其他构造方法,本类其他构造方法再通过super去手动调用父类的带参的构造方法
注意: this(…)super(…) 必须放在构造方法的第一行有效语句,并且二者不能共存
方法重写
-
1、方法重写概念
-
子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
-
-
2、方法重写的应用场景
-
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
-
-
3、Override注解
-
用来检测当前的方法,是否是重写的方法,起到【校验】的作用
-
方法重写的注意事项
-
方法重写的注意事项
-
私有方法不能被重写(父类私有成员子类是不能继承的)
-
子类方法访问权限不能更低(public > 默认 > 私有)
-
静态方法不能被重写,如果子类也有相同的方法,并不是重写的父类的方法
抽象类
当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了!
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类!
特点
-
抽象类和抽象方法必须使用 abstract 关键字修饰
//抽象类的定义 public abstract class 类名 {} //抽象方法的定义 public abstract void eat();
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
-
抽象类不能实例化
-
抽象类可以有构造方法
-
抽象类的子类
要么重写抽象类中的所有抽象方法
要么是抽象类
代码块
在Java中,使用 { } 括起来的代码被称为代码块
代码块分类
局部代码块
-
位置: 方法中定义
-
作用: 限定变量的生命周期,及早释放,提高内存利用率
public class Test {
/*
局部代码块
位置:方法中定义
作用:限定变量的生命周期,及早释放,提高内存利用率
*/
public static void main(String[] args) {
{
int a = 10;
System.out.println(a);
}
// System.out.println(a);
}
}
构造代码块
-
位置: 类中方法外定义
-
特点: 每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
-
作用: 将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
public class Test { /* 构造代码块: 位置:类中方法外定义 特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行 作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性 */ public static void main(String[] args) { Student stu1 = new Student(); Student stu2 = new Student(10); } } class Student { { System.out.println("好好学习"); } public Student(){ System.out.println("空参数构造方法"); } public Student(int a){ System.out.println("带参数构造方法..........."); } }
静态代码块
-
位置: 类中方法外定义
-
特点: 需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
-
作用: 在类加载的时候做一些数据初始化的操作
public class Test { /* 静态代码块: 位置:类中方法外定义 特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次 作用:在类加载的时候做一些数据初始化的操作 */ public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(10); } } class Person { static { System.out.println("我是静态代码块, 我执行了"); } public Person(){ System.out.println("我是Person类的空参数构造方法"); } public Person(int a){ System.out.println("我是Person类的带...........参数构造方法"); } }
接口
-
接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。
-
Java中接口存在的两个意义
-
用来定义规范
-
用来做功能的拓展
-
特点
-
接口用关键字interface修饰
public interface 接口名 {}
-
类实现接口用implements表示
public class 类名 implements 接口名 {}
-
接口不能实例化
我们可以创建接口的实现类对象使用
-
接口的子类
要么重写接口中的所有抽象方法
要么子类也是抽象类
成员特点
-
成员特点
-
成员变量
只能是常量 默认修饰符:public static final
-
构造方法
没有,因为接口主要是扩展功能的,而没有具体存在
-
成员方法
只能是抽象方法
默认修饰符:public abstract
关于接口中的方法,JDK8和JDK9中有一些新特性,后面再讲解
-
类和接口的关系
-
类与类的关系
继承关系,只能单继承,但是可以多层继承
-
类与接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
-
接口与接口的关系
继承关系,可以单继承,也可以多继承
接口中默认方法
-
格式
public default 返回值类型 方法名(参数列表) { }
-
作用
解决接口升级的问题
-
范例
public default void show3() { }
-
注意事项
-
默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
-
public可以省略,default不能省略
-
如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
-
接口中静态方法
-
格式
public static 返回值类型 方法名(参数列表) { }
-
范例
public static void show() { }
-
注意事项
-
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
-
public可以省略,static不能省略
-
接口中私有方法
-
私有方法产生原因
Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性
-
定义格式
-
格式1
private 返回值类型 方法名(参数列表) { }
-
范例1
private void show() { }
-
格式2
private static 返回值类型 方法名(参数列表) { }
-
范例2
private static void method() { }
-
-
注意事项
-
默认方法可以调用私有的静态方法和非静态方法
-
静态方法只能调用私有的静态方法
-
内部类
-
内部类概念
-
在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
-
-
内部类定义格式
-
格式&举例:
/* 格式: class 外部类名{ 修饰符 class 内部类名{ } } */ class Outer { public class Inner { } }
-
-
内部类的访问特点
-
内部类可以直接访问外部类的成员,包括私有
-
外部类要访问内部类的成员,必须创建对象
-
示例代码:
/* 内部类访问特点: 内部类可以直接访问外部类的成员,包括私有 外部类要访问内部类的成员,必须创建对象 */ public class Outer { private int num = 10; public class Inner { public void show() { System.out.println(num); } } public void method() { Inner i = new Inner(); i.show(); } }
成员内部类
-
-
成员内部类的定义位置
-
在类中方法,跟成员变量是一个位置
-
-
外界创建成员内部类格式
-
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
-
举例:Outer.Inner oi = new Outer().new Inner();
-
私有成员内部类
将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。
静态成员内部类
静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
静态成员内部类中的静态方法:外部类名.内部类名.方法名();
局部内部类
-
局部内部类定义位置
-
局部内部类是在方法中定义的类
-
-
局部内部类方式方式
-
局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
-
该类可以直接访问外部类的成员,也可以访问方法内的局部变量
-
匿名内部类
-
匿名内部类的前提
-
存在一个类或者接口,这里的类可以是具体类也可以是抽象类
-
-
匿名内部类的格式
-
格式:new 类名 ( ) { 重写方法 } new 接口名 ( ) { 重写方法 }
-
举例:
new Inter(){ @Override public void method(){} }
-
-
匿名内部类的本质
-
本质:是一个继承了该类或者实现了该接口的子类匿名对象
-
-
匿名内部类的细节
-
匿名内部类可以通过多态的形式接受
-
Inter i = new Inter(){
@Override
public void method(){
}
}
匿名内部类直接调用方法
interface Inter{
void method();
}
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
匿名内部类在开发中的使用
-
匿名内部类在开发中的使用
-
当发现某个方法需要,接口或抽象类的子类对象,我们就可以传递一个匿名内部类过去,来简化传统的代码
-
/*
游泳接口
*/
interface Swimming {
void swim();
}
public class TestSwimming {
public static void main(String[] args) {
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});
}
/**
* 使用接口的方法
*/
public static void goSwimming(Swimming swimming){
/*
Swimming swim = new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
}
*/
swimming.swim();
}
}
Lambda表达式
-
函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现
标准格式
-
格式:
(形式参数) -> {代码块}
-
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
-
->:由英文中画线和大于符号组成,固定写法。代表指向动作
-
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
-
-
组成Lambda表达式的三要素:
-
形式参数,箭头,代码块
-
-
Lambda表达式的使用前提
-
有一个接口
-
接口中有且仅有一个抽象方法
-
表示使用
无参无返回值抽象方法
() -> {
System.out.println("一天一苹果,医生远离我");}
打印 一天一苹果,医生远离我
有参无返回值抽象方法
(String s) -> {
System.out.println(s);
System.out.println("飞机自驾游");
}
有参有返回值抽象方法
(int x,int y) -> {
return x + y;
}
Lambda表达式的省略模式
-
省略的规则
-
参数类型可以省略。但是有多个参数的情况下,不能只省略一个
-
如果参数有且仅有一个,那么小括号可以省略
-
如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
public interface Addable { int add(int x, int y); } public interface Flyable { void fly(String s); } public class LambdaDemo { public static void main(String[] args) { // useAddable((int x,int y) -> { // return x + y; // }); //参数的类型可以省略 useAddable((x, y) -> { return x + y; }); // useFlyable((String s) -> { // System.out.println(s); // }); //如果参数有且仅有一个,那么小括号可以省略 // useFlyable(s -> { // System.out.println(s); // }); //如果代码块的语句只有一条,可以省略大括号和分号 useFlyable(s -> System.out.println(s)); //如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉 useAddable((x, y) -> x + y); } private static void useFlyable(Flyable f) { f.fly("风和日丽,晴空万里"); } private static void useAddable(Addable a) { int sum = a.add(10, 20); System.out.println(sum); } }
-
Lambda表达式的使用前提
-
使用Lambda必须要有接口
-
并且要求接口中有且仅有一个抽象方法
Lambda表达式和匿名内部类的区别
-
所需类型不同
-
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
-
Lambda表达式:只能是接口
-
-
使用限制不同
-
如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
-
如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
-
-
实现原理不同
-
匿名内部类:编译之后,产生一个单独的.class字节码文件
-
Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
-