1.面向对象概述
概述:如今主流的软件开发思想主要有两种:面向对象开发和面向过程开发。面向过程出现得比较早,典型代表是C语言,开发中小型项目的效率很高,但很难适用于如今主流的大中型项目开发场景。面向对象出现的更晚一点,典型代表有java或C++等语言,更加适合用于大型开发场景。
面向过程思想与面向对象思想的对比:
1.面向过程的思想:当需要实现一个功能的时候,看中的是每一个步骤怎么做,整体的过程是怎么样的,每一个操作都需要自己搞
2.面向对象的思想:当需要实现一个功能的时候,不看具体的过程和步骤,而是关心“谁来帮我做这件事情”
面向对象的三大特征:封装、继承和多态
2.面向对象的代码体验
import java.util.Arrays;
public class Demo {
public static void main(String[] args) {
//面向过程的思想遍历数组
int[] array = {1, 2, 3, 4};
System.out.print("[");
for(int i = 0; i < array.length; i ++){
if(i == array.length - 1){
System.out.print(array[i]);
}else{
System.out.print(array[i] + ", ");
}
}
System.out.println("]");
System.out.println("==============");
//面向对象的思想遍历数组
//使用jdk为我们提供的Arrays对象来帮我们把数组转换为指定的格式
System.out.println(Arrays.toString(array));
}
}
3.类与对象的关系
在了解类与对象的关系之前,我们需要先了解两个概念。
1.属性:事物的特征描述信息,“是什么”
2.行为:事物的能力行动方案,“能做什么”
面向对象的语言中,“类”就是用来模拟现实事物的,类中也有属性、行为两个组成部分,而“对象”是类的具体事例,例如:
1.类:抽象的,“一张手机设计图”
2.对象:具体的,"一部具体的手机”
类的定义:类是用来模拟现实事物的代码手段,事务分为属性、行为两个方面,类当中也对应地包含两个部分
1.成员变量(属性):将变量位置直接定义在类中,在方法外,就是成员变量
2.成员方法(行为):将普通的方法去掉static关键字,即为成员方法
创建一个类的例子:创建一个学生类:
public class Student {
//成员变量
public String name;
public int age;
//成员方法
public void eatting(){
System.out.println("eating!");
}
public void studying(){
System.out.println("studying!!");
}
}
但你仅仅创建一个类,类通常情况下是无法单独使用的,如果想要使用这个类,就需要根据类来创建一个真正的对象来使用,代码如下:
public class Demo5 {
public static void main(String[] args) {
//创建了一个对象,是一个名为stu1的学生,对象的名称stu1也叫做对象的“引用名”
Student stu1 = new Student();
}
}
那么,如何使用这个已经创建好的stu1对象的成员变量和成员方法呢?
1.使用成员变量:格式为 对象名.成员变量名
2.使用成员方法:格式为 对象名.成员方法名(),这里的小括号里可以传参
具体代码实现如下:
public class Demo5 {
public static void main(String[] args) {
//创建了一个对象,是一个名为stu1的学生,对象的名称stu1也叫做对象的“引用名”
Student stu1 = new Student();
System.out.println(stu1.name);//null,成员变量未赋值前有默认值
System.out.println(stu1.age);//0
System.out.println("===============");
stu1.name = "luyi";
stu1.age = 18;
System.out.println(stu1.name);//luyi
System.out.println(stu1.age);//18
stu1.eatting();//eating!
stu1.studying();//studying!!
}
}
代码:Student stu1 = new Student();做了什么事情?
1.把student.class文件加载到内存
2.在栈内存为stu1开辟空间
3.在堆内存为学生对象申请空间
4.给学生的成员变量进行默认初始化 null和0
5.给学生的成员变量进行显示初始化 luyi 18
6.通过构造方法给成员变量进行初始化
7.对象构造完毕,把地址赋给stu1变量
4.局部变量与成员变量的区别
1.定义的位置不同
局部变量:定义在方法内部里
成员变量:直接定义在类当中,在方法的外面的
2.内存中的位置不一样
局部变量:在栈内存中
成员变量:在堆内存中
3.生命周期不同
局部变量:随着方法的进栈而出现,随着方法的出栈而消失
成员变量:随着对象被创建而出现,随着对象被JVM回收而消失
4.默认值不一样
局部变量:没有默认值
成员变量:如果没有被赋值,那么将会有一个默认值
5.匿名对象:没有名字的对象
1.调用方法,仅仅只调用一次的时候
2.可以作为实际参数传递
3.格式:new 类名()
6.private关键字:确保数据的安全
问题描述:当你使用类里的成员变量时,无法控制它的取值范围,数据不安全
解决方案:使用private关键字
如何使用private关键字?
使用方法为在成员变量之前加上private,如private int age,一旦使用了private关键字对成员进行修饰,那么超出本类范围之外,就不能直接访问了,但是可以通过给private成员变量编写一对Setter和Getter方法来间接访问数据
private使用的例子:
//Person.java
public class Person {
public String name;
private int age;
public void show(){
System.out.println("我是" + name + ", 今年" + age + "岁");
}
public int getAge() {
return age;
}
public void setAge(int age) {
//多了一步判断,对不合理的数据进行了过滤
if(age <= 0){
System.out.println("数据错误");
}
this.age = age;
}
}
//Demo6.java
public class Demo6 {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "luyi";
p1.show();//我是luyi, 今年0岁
p1.setAge(-20);//数据错误
p1.show();//我是luyi, 今年-20岁
p1.setAge(20);//我是luyi, 今年20岁
p1.show();
}
}
7.封装
1.概述:隐藏实现细节,提供公共的访问方法
2.好处:
- 隐藏实现细节,提供公共的访问方式
- 提高代码的复用性;
- 提高代码的安全性;
3.设计原则:把不想让外界知道的实现细节给隐藏起来,提供公共的访问方式
4.前面讲的privare关键字就是封装的一种体现
8.this关键字
当局部变量和成员变量重名时,方法当中会根据“就近原则”使用局部变量
如果希望区分一下,就需要使用this.成员变量名来与局部变量进行区分
记住一句非常非常重要的话:通过谁调用的方法,谁就是this
9.构造方法
概述:构造方法就是专门用来创建对象的方法,当通过new关键字创建对象时,其实就是在调用构造方法
如何定义一个构造方法呢?
格式为:
public 构造方法名(参数类型 参数名称){
方法体;
return;//通常最后一行的return省略不写
}
调用构造方法的格式就是创建对象的格式:
new 类名称()
构造方法的注意事项:
1.构造方法不能写返回值类型,连void都不能写
2.构造方法的名称必须和所在的类名称完全一样,大小写也一样
3.构造方法可以重载
4.构造方法如果没有自定义,那么会默认赠送一个,如果自定义了至少一个构造方法,那么就不再赠送构造方法
5.构造方法没有返回值,但是我们可以在方法的最后写上:return;
构造方法的简单例子:
//Animal.java
public class Animal {
//构造方法
public Animal(){
System.out.println("执行了构造方法");
}
}
//Demo7.java
public class Demo7 {
public static void main(String[] args) {
// 调用了构造方法
Animal dog = new Animal();
}
}
构造方法的重载及好处
我们从一个例子就可以看出来:
//Animal.java
public class Animal {
private String name;
private String voice;
//构造方法
public Animal(){
System.out.println("执行了构造方法");
}
public Animal(String name, String voice){
System.out.println("执行了带参数构造方法");
this.name = name;
this.voice = voice;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVoice() {
return voice;
}
public void setVoice(String voice) {
this.voice = voice;
}
}
//Demo7.java
public class Demo7 {
public static void main(String[] args) {
// 调用了构造方法
Animal dog = new Animal();
System.out.println("===============");
//走的是没有参数的构造方法
Animal sheep = new Animal();
sheep.setName("羊");
sheep.setVoice("咩咩咩");
System.out.println("动物:" + sheep.getName() + "叫声:" + sheep.getVoice());
System.out.println("=================");
//使用构造方法重载使得在new 创建一个对象时就可以传入数据值
//重载构造方法的好处就是可以不用单独传入数据值,直接在new一个对象时传入参数即可
Animal cat = new Animal("猫", "喵喵喵");
System.out.println("动物:" + cat.getName() + "叫声:" + cat.getVoice());
}
}
10.如何定义一个标准类
1.所有的成员变量都需要使用private关键字私有化
2.为每一个成员变量编写一对Setter和Getter
3.编写一个无参数的构造方法
4.编写一个全参数的构造方法
有这四个标准的类就叫做POJO类
注意:对于所有的数据类型,Getter都必须getXxx,Setter都必须叫setXxx,但是有一个特例,如果是boolean值的话,那么setXxx规则不变,而getXxx需要写成isXxx的形式,这样才能叫做一个POJO类,但是写成getXxx的形式也不会报错,只是更加规范
11.Math类
Math类是针对数学进行操作的类
没有构造方法,因为它的成员都是静态的
产生随机数的方法:public static double random();//随机数的范围为[0.1, 1.0]
产生一个1-100之间的随机数代码:
int number = (int)(Math.random()*100+1);
12.代码块
根据位置和声明的不同,代码块可分为三种:
1.局部代码块:定义在方法中的,用于限定变量的生命周期,及早释放,提高内存利用率
格式:{代码块}
2.构造代码块:在类中的成员位置,把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造方法块,作用是对对象进行初始化
格式:比静态代码块少了static:{代码块}
3.静态代码块:对类的数据进行初始化,仅仅只执行一次,作用是对类进行初始化
格式:比构造代码块多了static:static{代码块}
示例代码:
public class Demo12 {
{
System.out.println("这是构造代码块");
};
public Demo12(){
System.out.println("这是构造方法");
}
static{
System.out.println("这是静态代码块");
};
public static void main(String[] args) {
System.out.println("调用了main方法");
new Demo12();//new一个对象时才会执行构造代码块
{
int num = 1;
System.out.println("这是局部代码块定义的num,值为" + num);
}
//System.out.println(num);//访问不到局部代码块里定义的num
};
}
静态代码块,构造代码块,构造方法的顺序问题?
静态代码块(只执行一次)>构造代码块(每次调用构造方法都执行)>构造方法
13.继承
概述:
把多个类中相同的成员给提取出来定义到一个独立的类中,然后让这多个类和该独立的类产生一个关系
格式
用关键字extends表示继承:格式为
class 子类名 extends 父类名
继承的好处
1.提高了代码的复用性
2.提高了代码的维护性
3.让类与类之间产生了一个关系,是多态的前提
继承的弊端:
1.让类的耦合性增强,这样某个类的改变,就会影响其他和该类相关的类
2.打破了封装性
java中继承的特点:
1.只支持单继承
2.可以多层继承
继承的注意事项:
1.子类不能继承父类的私有成员
2.子类不能继承父类的构造方法,但是可以通过super去访问
3.不要为了部分功能而去继承
什么时候使用继承呢?
继承体现的是: is a 的关系
如有一个学生类和人类,你想用学生类去继承人类,这个时候,你就判断一下学生是否是一个人,结果是是的,所以就使用继承来继承人这个类吧
java继承中的成员关系
1.成员变量
- 子类的成员变量名称和父类中的成员变量名称不一样,那就跟无继承的使用类一样的操作
- 子类的成员变量名称和父类中的成员变量名称一样,这个怎么访问呢?
- 在子类方法的局部范围找,有就使用
- 在子类的成员范围找,有就使用
- 在父类的成员范围找,有就使用
- 如果还找不到,那就只能报错了
2.构造方法
- 子类的构造方法默认会去访问父类的无参构造方法,这是因为子类会继承父类中的数据,可能还会使用父类的数据,所以,子类初始化之前,一定要先完成父类数据的初始化,而且,子类每一个构造方法的第一条语句默认都是super()
- 父类如果没有无参数构造方法,怎么办?
- 子类通过super去明确调用带有参数的构造方法
- 子类通过this调用本身的其他构造方法去调用父类的带参方法,一定要有一个去访问父类的构造方法,否则父类数据就没有初始化了
3.成员方法
- 子类的成员方法和父类中的成员方法名称不一样, 那就跟无继承的类的使用一样
- 子类的成员方法和父类的成员方法名称不一样,这个怎么访问?
- 在子类中找,有就使用
- 在父类中找,有就使用
- 如果还找不到,就报错
方法重写(override)
概述:子类中出现了父类中方法声明一模一样的方法
与方法重载(overload)的区别:方法重载是本类中出现的方法名一样,参数列表不同的方法,与返回值无关,因此重载也就无法改变返回值类型了,重载是对某一个类来说的,而方法重写则是针对父子类继承来说的
方法重写的应用:当子类需要父类的功能,而功能主体子类有自己特有的内容时,可以重写父类的方法
方法重写的注意事项:
1.父类中私有方法不能被重写,因为父类的私有方法根本就不会被子类继承
2.子类重写父类方法时,访问权限不能更低,最好就是一致,如父类的方法的访问权限为public,那子类重写方法时就访问权限就必须为public
3.父类静态方法,子类也必须通过静态方法进行重写,其实这个不算方法重写,但是现象确实如此,至于为什么算不上方法重写,后面多态中会讲
代码示例:
//Person.java
public class Person {
private String name;
private int age;
public Person(){
//父类的无参构造方法
}
public Person(String name, int age) {
//父类的带参构造方法
this.name = name;
this.age = age;
}
public void doThings(){
System.out.println("living...");
}
public void run(){
System.out.println("i am running");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//Demo1.java
public class Demo1 {
public static void main(String[] args) {
//创建一个学生对象
Student s1 = new Student("张三" , 18);
//创建一个老师对象
Teacher t1 = new Teacher("李四", 20);
//调用学生类重写的父类的doThings方法
s1.doThings();
//调用学生类自己的方法
s1.eat();
//调用老师类重写的父类的doThings方法
t1.doThings();
System.out.println("老师:" + t1.getName() + ",年龄:" + t1.getAge());
}
}
class Student extends Person{
public Student(){};
public Student(String name, int age){
//默认调用父类的无参构造方法,加super()与不加的意义一样
super();
}
@Override //重写的注解
//重写父类的doTings方法
public void doThings(){
System.out.println("studying...");
}
//自己的eat方法
public void eat(){
System.out.println("i like eat checken");
}
}
class Teacher extends Person{
public Teacher(){};
public Teacher(String name, int age){
//调用父类的带参构造方法
super(name, age);
}
@Override
public void doThings(){
//通过super去调用了父类的成员方法
super.doThings();
System.out.println("teaching");
}
}
15.this与super的区别和应用
this(...)和super(...)必须放在第一条语句,否则,就会有父类数据的多次初始化
区别:
1.this代表本类对应的引用
2.super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)
应用:
1.调用成员变量:
- this.成员变量:调用本类的成员变量
- super.成员变量:调用分类的成员变量
2.调用构造方法:
- this(...):调用本类的构造方法
- super(...):调用父类的构造方法
3.调用成员方法
- this.成员方法:调用本类的成员方法
- super.成员方法:调用父类的成员方法
16.final关键字
由于继承中父类的方法可以被重写,所以,父类的功能,就会被子类给覆盖,有些时候,我们不想让子类去覆盖父类的功能,只能让它使用,这个时候,就可以使用我们的final关键字了,使用了final关键字的方法就不能被子类重写了
final不仅可以修饰方法,它还可以修饰类和变量
final修饰这三种的特点:
- final可以修饰类,被final修饰的类,则这个类不能被继承
- final可以修饰方法,该方法不能被重写
- final可以修饰变量,该变量不能被重新赋值,因为这个变量其实就是常量,其实常量有两种,字面值常量和自定义常量,而被final修饰的变量就是自定义常量
final修饰基本类型变量与引用类型变量时的区别:
1.final修饰基本类型时,基本类型的值不能发生改变
2.final修饰引用类型时,引用类型的地址不能发生改变,但是,该对象的堆内存的值是可以发生改变的
17.多态
概述:某一个对象(事物),在不同时刻表现出来的不同状态,多态有具体类多态,抽象类多态,还有接口多态
前提:要有继承关系;要有方法重写(其实没有也可以,但是没意义);要有父类引用指向对象(即父类 f = new 子类())
多态中的成员访问特点:
- 成员变量:编译看左边,运行看右边
- 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化
- 成员方法:编译看左边,运行看右边
- 静态方法:编译看左边,运行看左边(静态和类相关,算不上重写,所以,访问还是左边)
- 总结:由于成员方法存在方法重写,所以它运行看左边
示例代码证明成员访问特点:
class Father{
int num1 = 10;
public void show(){
System.out.println("show father");
}
public void fatherMehotd(){
System.out.println("this is father's method");
}
public static void staticMethod(){
System.out.println("this is father's staticMethod");
}
}
class Son extends Father{
int num2 = 10;
public void show(){
System.out.println("show son");
}
public void sonMethod(){
System.out.println("this is son's method");
}
public static void staticMethod(){
System.out.println("this is son's static Mehod");
}
}
public class Demo1 {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.num1);
//报错,找不到符号(右边子类的num2访问不到)
//System.out.println(f.num2);
f.fatherMehotd();
//报错,找不到符号(右边子类的sonMehod方法访问不到)
//f.sonMehod();
f.show();//show son,执行的是子类的show
f.staticMethod();//this is father's staticMethod,执行的是父类的staticMehod
}
}
多态的好处
1.提高了代码的维护性
2.提高了代码的扩展性
多态的弊端
不能使用子类的特有功能
多态的向上转型和向下转型
前面说多态的弊端就是不能使用子类的特有功能,那么,我们就真的不能使用子类的特有功能了么?答案当然是否定的,我们可以通过以下两种方法使用子类的特有功能
1.创建子类对象调用方法即可,但是很多时候不合理,而且,太占内存了
2.把父类的引用强制转换为子类的引用(向下转型)
由此,也引出了我们多态的向上转型与向下转型的概念
- 向上转型的格式:Father f = new Son()
- 向下转型的格式: Son s = (Son)f;
示例代码:
class Animal{
public void eat(){
System.out.println("吃饭");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃*");
}
public void lookDoor(){
System.out.println("狗看猫");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void playGame(){
System.out.println("猫玩个球");
}
}
public class Demo2 {
public static void main(String[] args) {
Animal a = new Dog();
a.eat();//调用狗类的eat方法
//a.lookDoor();//报错
Dog d = (Dog)a;//向下转型
d.lookDoor();
a = new Cat();
a.eat();//调用的是猫类的eat方法
//a.playGame();//报错
Cat c = (Cat)a;
c.playGame();
//此时的a是猫类,无法向下转型为狗类
Dog dd = (Dog)a;//报错:ClassCastException
}
}
18.抽象类
概述:在java中,我们把一个不是具体的功能称为抽象方法,如前面举例的动物类的吃饭方法,由于吃饭的内容不一样,所有吃饭的方法是抽象的,而类中如果有抽象方法,该类必须定义为抽象类
特点:
- 抽象类和抽象方法必须用abstract关键字修饰
- 抽象类中不一定有抽象方法,但是有抽象方法的类必须定义为抽象类
- 抽象类不能实例化,因为它不是具体的,抽象类有构造方法,但是不能实例化,它是用于子类访问父类数据的初始化
- 如果不想重写抽象方法,该子类是一个抽象类,重写所有的抽象方法,这个时候子类是一个具体的类
- 抽象类如果要实例化,就得靠具体的子类实现,是多态的方式
抽象类的成员特点:
- 成员变量:既可以是变量,也可以是常量
- 构造方法:有构造方法,用于子类访问父类数据的初始化
- 成员方法:既可以是抽象方法,也可以是非抽象的方法。如果是抽象方法,不能有方法体,作用是强制要求子类做一些事情,如果是非抽象方法,子类继承的事情,提高代码的复用性
abstract不能和哪些关键字共存:
- private:私有方法不会被继承,但是abstract抽象方法必须被子类重写,这就冲突了
- final:final定义的方法是不能被继承的,但是abstract抽象方法必须被子类重写,这也冲突了
- static:静态方法的访问可以通过类名去访问,而abstract修饰的方法没有方法体,访问就没意义了
示例代码:
//Teacher.java
public abstract class Teacher {
private String name;
private int age;
public Teacher(){};
public Teacher(String name, int age){
this.name = name;
this.age = age;
}
public abstract void teach();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//Demo3.java
class MathTeacher extends Teacher{
public MathTeacher(String name, int age) {
super(name, age);
}
public void teach() {
System.out.println("i teach math");
}
}
class EnglishTeacher extends Teacher{
public EnglishTeacher(String name, int age){
super(name, age);
}
public void teach(){
System.out.println("i teach english");
}
}
public class Demo3 {
public static void main(String[] args) {
Teacher t1 = new MathTeacher("张东升", 40);
Teacher t2 = new EnglishTeacher("朱朝阳" , 20);
t1.teach();
t2.teach();
}
}
19.接口
概述
接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征,没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的功能,总结一句话就是接口是用来扩展功能的
接口的基本定义格式
public interface 接口名称{
}
接口的特点
1.接口用关键字interface表示
2.类实现接口用implements表示
3.接口不能实例化:如果想实例化接口,那就按照多态的方式,由具体的子类实例化,其实这也是多态的一种,接口多态
4.接口的子类要么是抽象类,要么重写接口中的所有抽象方法
接口的成员特点:
1.成员变量:只能是常量,并且是静态的,它会有个默认修饰符为public static final,因此建议手动给出默认修饰符
2.构造方法:接口没有构造方法,使用接口的类是继承自超类Object,其实所有的类都默认继承自Object
3.成员方法:只能是抽象方法,有默认修饰符public abstract,因此建议手动给出默认修饰符
类与类,类与接口,接口接口的关系
1.类与类的关系:继承关系,只能是单继承,可以多层继承
2.类与接口的关系:实现关系,可以单实现,也可以多实现
3.接口与接口的关系:继承关系,可以单继承,也可以多继承
实例代码证明以上说法:
interface Inte1{
}
interface Inte2{
}
class Person{
}
class Father{
}
//类的多继承报错
/*class Son extends Person, Father{
}*/
//接口多实现
class IntefaceDemo1 implements Inte1, Inte2{
}
//接口多继承
interface IntefaceSon extends Inte1, Inte2{
}
public class Demo1 {
public static void main(String[] args) {
}
}
简单的接口及抽象方法实现代码
//动物接口
public interface Animal {
//定义一个吃饭的抽象方法
public abstract void eating();
//不带修饰符的定义了一个睡觉的抽象方法
void sleeping();
}
实现类的定义
如果想要使用定义好的接口,必须有一个接口的“实现类”
定义实现的格式为:
public class 实现类名称 implements 接口名称{
//一定要覆盖重写所有的方法
}
实现类的代码示例:
public class Cat implements Animal{
public void eating() {
System.out.println("猫在吃");
}
public void sleeping() {
System.out.println("猫在睡");
}
}
使用接口和实现类
1.创建:接口名称 引用名 = new 实现类名称()
2.调用:引用名.抽象方法名(参数);
注意:
1.左边是接口类型,那么只能调用接口当中定义好的内容,不能调用右侧实现类当中特有的内容(接口隔离)
2.当调用接口当中的抽象方法时,真正进行运行的是右侧new的时候类的具体方法内容
3.总结一句话,编译看左边,运行看右边
实例代码:
//Animal.java
public interface Animal {
//定义一个吃饭的抽象方法
public abstract void eating();
//不带修饰符的定义了一个睡觉的抽象方法
void sleeping();
}
//Cat.java
public class Cat implements Animal{
public void eating() {
System.out.println("猫在吃");
}
public void sleeping() {
System.out.println("猫在睡");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
//Demo7.java
public class Demo7 {
public static void main(String[] args) {
Animal cat = new Cat();
cat.eating();//猫在吃
cat.sleeping();//猫在睡
//cat.catchMouse();//错误,无法调用右侧实现类专有的方法
}
}
抽象类与接口的区别
1.成员区别:
抽象类:
- 成员变量:可以变量,也可以是常量
- 构造方法:有构造方法
- 成员方法:可以抽象,也可以是非抽象
接口:
- 成员变量:只可以常量
- 构造方法:无
- 成员方法:只可以是抽象
2.设计理念的区别
抽象类:被继承体现的是“is a”的关系,抽象类中定义的是该继承体系的共性功能
接口:被实现的是“like a”的关系,接口中定义的是该继承体系的扩展功能(额外的功能)
面向接口编程
如果使用的功能,接口已经可以满足,那么就不在乎具体的类是谁,只在乎接口即可
20.Lambda表达式(java8才有)
在使用接口的时候,每次都需要创建三个东西(接口,实现类,main程序入口),可有时候只有一个抽象方法的接口,我们没有必要去创建一个接口实现类,我们可以通过Lambda表达式来为我们省去创建实现类的步骤
在认识Lambda表达式之前,我们先来看一个示例:
//Calculator.java
public interface Calculator {
public abstract int sum(int a, int b);
}
//Demo8.java
public class Demo8 {
public static void main(String[] args) {
//Lambda表达式(x, y) -> x + y
int result = method((x, y) -> x + y);
System.out.println(result);//300
}
public static int method(Calculator calc){
return calc.sum(100, 200);
}
}
上面这个例子中,Lambda表达式为(x, y)->(x + y)
method方法需要一个Calculator接口类型的参数,而Lambda表达式就是充当了Calculator接口类型的参数
初步理解:
1.lambda表达式前面的小括号,其实就是接口抽象方法的小括号
2.箭头代表拿着小括号的数据做什么事情,是一个指向的动作
3.箭头后面代表拿到了参数之后做什么事情
Lambda表达式的语义本身就代表了怎么做这件事情,没有对象的概念在里面,更加简单直观
前面我们简单的看了一下Lambda表达式的使用示例,接下来我们正式来介绍我们的Lambda表达式吧
使用Lambda表达式的前提是什么呢?前提就是必须有“函数式接口”
那么什么是函数式接口呢?函数式接口就是有且只有一个抽象方法的接口
那么如何才能万无一失的检测一下当前接口是不是函数式接口呢?
用一个固定的格式@FunctionInterface写在public interface之前一行即可:
@FunctionInterface
public interface 函数式接口名{
//...
}
@FunctionInterface可写可别写,这是一个可选的检测手段而已
Lambda表达式有两种格式,标准格式和简便格式
Lambda表达式的标准格式:
1.一些参数(方法参数)
2.一个箭头
3.一些代码(方法体,大括号)
例如抽象方法:
public abstract sum(int a, int b)可以翻译为
(int a, int b) -> { return a + b};
Lambda表达式的简便格式:
1.Lambda表达式当中的参数类型可以省略不写
2.如果参数有且只有一个,那么小括号可以省略
3.如果语句只有一个,那么大括号和return也可以省略
因此,抽象方法public abstract sum(int a, int b)可以翻译为标准格式(int a, int b) -> { return a + b};也可以写成简便格式(a, b)-> a + b;这也就是实例中的写法
Lambda的上下文推断:也就是为什么Lambda表达式可以推断出是哪个函数式接口,他是根据调用方法时,参数类型是某一个函数式接口,因此Lambda可以推断出来是哪个接口
21.链式编程
链式编程的特点是每次调用完毕后,返回的是一个对象
示例代码:
class Student{
public void study(){
System.out.println("studying...");
}
}
class StudentDemo{
public Student getStudent(){
return new Student();
}
}
public class Demo4 {
public static void main(String[] args) {
StudentDemo demo = new StudentDemo();
//链式编程
demo.getStudent().study();//studying...
}
}
22.包(package)
概述:包其实就是文件夹,作用是把相同的类名放到不同的包下,对类进行分类管理,
包的分类:我们在进行包的使用的时候,可以把包按功能分,也可以按模块分
包的格式:package 包名;多级包用“.”分开
注意事项:
1.package语句必须是程序的第一条可以执行的代码
2.package语句在一个java文件中只能有一个
3.如果没有package,默认表示无包名
带包的编译与运行:
1.手动式
- 编写一个带包的java文件
- 通过javac命令编译该java文件
- 手动创建包名
- 把第二个步骤的class文件放到第三个步骤的最底层包
- 回到和根目录在同一目录的地方,然后运行(带包运行)
2.自动式
- 编写一个带包的java文件
- javac编译的时候带上-d即可(如javac -d . Hello.java,这个“.”就表示在当前文件夹下创建这个包)
- 回到和包根目录在同一目录的地方,然后运行(带包运行)
import导包:
当我们使用不同包下的类时,如果每次调用都要使用包名作为前缀来调用java类,那么就太麻烦了,而导包正是帮我们解决这个问题的
格式:import 包名(如import com.luyi.Demo);这种方式导入是到类的名称,注意:我们用谁就导谁,不建议使用“*”导入包下的所有的类
注意:package,import和class在一个文件中的顺序应该是package->import->class,而且在一个java文件中,package关键字只能有一个,import关键字可以有多个,class关键字可以有多个但是建议是一个
23.权限修饰符
权限修饰符包括private,default(默认),protected,public
不同修饰符的访问权限如下图:
修饰符
1.权限修饰符:private,默认的,protected,public
2.状态修饰符:static和final
3.抽象修饰符:abstract
类,成员变量,构造方法,成员方法可以使用的修饰符
1.类:
- 权限修饰符:默认的,public(但是private可以修饰内部类)
- 状态修饰符:final
- 抽象修饰符:abstract
- 用的最多的是public
2.成员变量:
- 权限修饰符:private,默认的,protected,public
- 状态修饰符:static, final
- 用的最多的是private
3.构造方法:
- 权限修饰符:private,默认的,protected,public
- 用的最多的就是public
4.成员方法:
- 权限修饰符:private,默认的,protected,public
- 状态修饰符:static和final
- 抽象修饰符:abstract
- 用的最多的是public
24.内部类
概述:把类定义在其他类的内部,这个类就叫做内部类
内部类的访问特点:
1.内部类可以直接访问外部类的成员,包括私有
2.外部类要访问内部类的成员,必须创建对象
内部类的位置:
1.在成员位置定义的类,被成为成员内部类
2.在局部位置(外部类的方法体里面)定义的类,被成为局部内部类
成员内部类
成员内部类如何直接访问内部类的成员:外部类名.内部类名 对象 = 外部类对象.内部类对象;
成员内部类的修饰符:
1.一般使用private,为的是保证数据的安全性
2.如果使用了static的话,那它就是为了方便访问数据,需要注意的是,静态内部类访问的外部类数据必须用静态修饰
- 被static修饰后的成员内部类的方法可以是静态方法,也可以是非静态方法
- 访问被static修饰的成员内部类的访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
- 被static修饰的成员内部类的静态方法还可以通过以下格式访问:外部类.内部类.静态方法();
示例代码
class Outer{
private int num = 10;
//成员位置
class Inner{
//成员内部类
public void show(){
System.out.println("this is inner");
}
}
static class StaticInner{
//静态的成员内部类
public void show1(){
System.out.println("this is StaticInner's show1");
}
public static void show2(){
System.out.println("this is StaticInner's show2");
}
}
}
public class Demo5 {
public static void main(String[] args) {
//直接访问成员内部类的方法的格式
Outer.Inner demo = new Outer().new Inner();
demo.show();//this is inner
//访问被静态修饰符修饰的成员内部类的格式
Outer.StaticInner demo2 = new Outer.StaticInner();
demo2.show1();//this is StaticInner's show1
Outer.StaticInner.show2();//this is StaticInner's show2
}
}
局部内部类
1.可以直接访问外部类的成员
2.在局部位置,可以创建内部类对象,通过对象调用内部类方法,来使用局部内部类的功能
需要注意的是局部内部类访问局部变量的注意事项
1.局部内部类访问的局部变量必须要用final修饰(java8之后就默认为这个局部变量加上了final修饰符,建议自己手动加上)
2.为什么要用final修饰呢?
因为局部变量是随着方法的调用而调用,随着调用完毕而消失的,而我们new出来的局部对象是在堆内存的,堆内存的内容并不会立即消失,那么,为了让数据还能继续被使用,就得用final修饰了,加入final之后,这个变量就变成了常量,既然是常量,那么在内存中存储得就是数据20,所以,就可以继续使用这个数据了
示例代码:
class Outer{
private int num1 = 10;
public void method(){
//java8之后不加final也不会报错,因为会自动加上去
final int num2 = 100;
class Inner{
//局部位置,局部内部类
public void show(){
System.out.println("这是局部内部类Inner的show方法");
}
public void showNum1(){
System.out.println("直接访问了外部类的变量:" + num1);
}
public void showNum2(){
System.out.println("访问了局部变量:" + num2);
}
}
Inner inner = new Inner();
inner.show();
inner.showNum1();
inner.showNum2();
}
}
public class Demo2 {
public static void main(String[] args) {
Outer demo = new Outer();
demo.method();
}
}
匿名内部类
概述:就是内部类的简写方法
匿名内部类的前提:存在一个类或者接口(类可以是具体类也可以是抽象类)
格式(这里new出来的其实就是一个类或者接口的子类或者实现类):
new 类名或者接口名(){重写方法;}
匿名内部类的本质就是一个继承了该类或者实现了该接口的子类匿名对象
使用这个匿名对象类的方法:
1.直接在定义好的匿名内部类后面“.”方法即可
2.但是如果有多个方法,就有点麻烦了,因此还可以通过以下格式实现:类(接口) 对象名 = new 类名或者接口名(){重写方法;}
示例代码:
interface Inter{
abstract public void show1();
abstract public void show2();
}
class Outer{
public void method(){
Inter i = new Inter(){
public void show1(){
//重写show1方法
System.out.println("show1");
}
public void show2(){
//重写show2方法
System.out.println("show2");
}
};
i.show1();
i.show2();
}
}
public class Demo1 {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
面试题
第一题:看程序写结果
须知知识点:
1.三种代码的执行流程;
2.静态代码块的内容会随着类的加载而加载;
3.子类初始化之前会先进行父类的初始化;
class Father{
static{
System.out.println("静态代码块:father");
}
{
System.out.println("构造代码块:father");
}
public Father(){
System.out.println("构造方法:father");
}
}
class Son extends Father{
static{
System.out.println("静态代码块:son");
}
{
System.out.println("构造代码块:son");
}
public Son(){
System.out.println("构造方法:son");
}
}
public class Demo2 {
public static void main(String[] args) {
Son son = new Son();//new一个Son对象出来
}
}
//结果:
静态代码块:father
静态代码块:son
构造代码块:father
构造方法:father
构造代码块:son
构造方法:son
第二题:看程序写结果
须知知识点:
1.成员变量的问题
- int x = 10;//成员变量是基本类型
- Student s = new Student();成员变量是引用类型
2.一个类的初始化过程
成员变量的初始化过程:默认初始化->显示初始化->构造方法初始化
3.子父类的初始化(分层初始化)
先进行父类初始化,然后进行子类初始化,虽然子类种构造方法默认有一个super(),但是初始化的时候,不是按照那个顺序进行的,而是按照分层初始化父类数据,再初始化子类数据
class X{
Y b =new Y();
X(){
System.out.println("X");
}
}
class Y{
Y(){
System.out.println("Y");
}
}
public class Z extends X{
Y a = new Y();
Z(){
System.out.println("Z");
}
public static void main(String[] args){
new Z();
}
}
//结果:
Y
X
Y
Z
第三题:看程序给出程序报错的地方
须知知识点:
1.final修饰基本类型时,基本类型的值不能发生改变
2.final修饰引用类型时,引用类型的地址不能发生改变,但是,该对象的堆内存的值是可以发生改变的
class Text{
int age = 10;
}
public class Demo3 {
public static void main(String[] args) {
int x = 10;
x = 100;
System.out.println(x);
final int y = 10;
y = 100;//报错,不能被重新赋值
final Text text = new Text();
System.out.println(text.age);
text.age = 100;//不报错,堆内存的值可以改变
text = new Text(); //报错,引用类型的地址不能发生改变
}
}
第四题:要求分别输出30,20,10
须知知识点:
1.内部类与外部类没有继承关系
2.通过外部类名限定this对象
class Outer1{
public int num = 10;
class Inner{
public int num = 20;
public void show(){
int num = 30;
System.out.println(___在这里打印出30___);
System.out.println(___在这里打印出20___);
System.out.println(___在这里打印出10___);
}
}
}
public class InnerClassText {
public static void main(String[] args) {
Outer1.Inner demo = new Outer1().new Inner();
demo.show();
}
}
//答案:
第一空:num
第二空:this.num
第三空:Outer.this.num或者new Outer().num