文章目录
- 一、类和对象
- 1. 面向对象
- 2. 类的定义
- 3. 对象的创建
- 4. 类在内存中的存储
- 5.类的成员使用
- 6. toString()方法
- 7. static 关键字
- 静态成员变量
- 静态成员方法
- 二、封装
- 1. 构造方法
- 概念基本使用
- 构造方法的重载
- this关键字
- 2. private
- 3. 封装的好处
- 三、继承
- 1. 继承的概念
- 2. extends
- 3. super关键字
- 4. Object
- 5. 更复杂的继承
- 6. final 关键字
- 7. 组和
- 四、多态
- 1. 向向转型
- 概念
- 向向转型发生的时机
- 注意事项
- 2. 向下转型
- instanceof关键字
- 3. 动态绑定(运行时绑定)
- 重写(@Override)
- 动态绑定的一个坑
- 4. 多态
- 多态的好处
- 五、访问权限
- 1. 包(package)
- 导入包中的类
- 静态导入
- 将类放到包中
- 2. 4种访问权限
- 六、代码块
- 1. 本地代码块
- 2. 静态代码块
- 3. 实例代码块
- 4. 执行顺序
- 七、抽象类
- 1.抽象类的定义
- 2. 抽象类的注意事项
- 3. 抽象类的意义
- 八、接口
- 1. 接口的定义
- 2. 接口中的方法和成员变量
- 3. default关键字
- 4. 接口的使用
- 5.实现多个接口
- 6. 接口的拓展
- 7. 接口使用示例
- 8. 抽象类和接口的区别
一、类和对象
1. 面向对象
面向过程和面向对象?
C语言就是典型的面向过程编程,它关注的实现过程。
Java是一个面向对象的语言,面向对象关注的是对象的创建和维护对象之间的关系,而不关系这个对象是怎么实现的。
举个列子:如果你要洗衣服
面向过程:拿盆子接水,放洗衣粉,手搓,再接水洗,把水倒掉拧干
面向对象:把衣服丢进洗衣机,放入洗衣粉,打开洗衣机
在这个过程中,洗衣机就可以当做一个对象,我们不必关心它的实现,只需拿过来用就可以了。
2. 类的定义
通过class
关键字就能创建一个对象。
class Student {
String name;
int age;
public void show() {
System.out.println("haha");
}
}
- 在类中定义的变量,称为成员变量或者字段。
- 在类中定义的方法,称为成员方法
注意:成员变量是在类的内部,方法的外部
3. 对象的创建
用类创建对象的过程成为实例化对象,通过new
关键字实例化一个对象。我们可以把类当做一个图纸或者模板,通过一个图纸可以建很多的房子,也就是说一个类可以实例化多个对象。
Student std1 = new Student();
Student std2 = new Student();
Student std3 = new Student();
匿名对象(只能使用一次)
没有引用的对象称为匿名对象.
new Student();
4. 类在内存中的存储
类和数组一样都属于引用,而引用指向的是一个对象,像上面的std1和std2都属于引用,局部变量是在栈上开辟内存的,引用里面存的是对象的地址,这些变量里存的都是引用,每实例化一个对象句在堆区开辟一块内存,对象里存的就是成员变量。
注意:类里面的方法并不是存在堆区的,方法本身是不占用内存的,方法是存在方法区的方法表中,在每个对象new好之后前面又几个字节存的就是方法表的地址,只有在调用这个方法时,才会通过方法表的地址找到这个方法,然后为这个方法在栈上开辟栈帧。
class Student {
//成员变量
String name;
int age;
// 成员方法
void show() {
System.out.println("haha");
}
}
public class Main {
public static void main(String[] args) {
Student std1 = new Student();
Student std2 = new Student();
Student std3 = new Student();
}
}
通过上图发现,其实并不是所有引用都是在栈上的,如果它是一个实例成员变量,那么它就是在堆上的,比如上图的name和age。
5.类的成员使用
成员变量的初始化,可以给类中的字段给一个默认值,但是不建议。
class Student {
//成员变量
String name = "zhangsan";
int age = 10;
// 成员方法
void show() {
System.out.println("haha");
}
}
更建议在使用时进行赋值
public static void main(String[] args) {
Student std1 = new Student();
std1.name = "李四";
std1.age = 18;
}
6. toString()方法
如果我们直接打印一个对象可能会出现下面的情况
public static void main(String[] args) {
Student std1 = new Student();
System.out.println(std1);
}
打印结果
Student@1b6d3586
因为如果我们的类没有自己实现toString方法,那么它将会调用Object的**toString()**方法,该方法是打印的是类名+@+hash值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
那么我们就可以重写(@Override)toString方法
class Student {
//成员变量
String name;
int age;
// 成员方法
void show() {
System.out.println("haha");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
那么打印的时候就是调用我们重写的toString方法,如果成员变量没有赋值打印出来的默认值。
@Override
是一个注解,它表示该方法是重写了父类的方法。
7. static 关键字
我们把成员变量分为普通成员变量和静态成员变量,普通成员变量也叫做实例成员变量,静态成员变量也叫做类变量
静态成员变量
count是被static
关键字修饰,它就是一个静态成员变量。我们可以通过对象来访问,也可以通过类名来访问。但是为了规范建议使用类名来访问静态成员变量,因为静态成员变量是不 依赖对象的。
class Student {
//成员变量
String name;
int age;
static int count;
// 成员方法
void show() {
System.out.println("haha");
}
}
public class Main {
public static void main(String[] args) {
Student std1 = new Student();
System.out.println(std1.count);
System.out.println(Student.count);
}
}
为了验证静态成员变量是不依赖对象的,可以写出这么一段代码
class Student {
//成员变量
String name;
int age;
static int count;
// 成员方法
void show() {
System.out.println("haha");
}
}
public class Main {
public static void main(String[] args) {
Student std1 = new Student();
std1.count++;
Student std2 = new Student();
std2.count++;
System.out.println(Student.count);
Student std = null;
System.out.println(std.count);
}
}
打印结果
2
2
说明std1和std2操作的都是同一个count,std=null
表示std不指向任何对象,而因为静态成员变量是不依赖于对象的所以最后一行代码是可以正常输出的。
所以
- 静态成员变量是不依赖于对象的
- 访问静态成员变量的方式更建议
类名.成员变量
静态成员方法
静态成员方法也是通过类名来访问使用的。
class Student {
//成员变量
String name;
int age;
static int count;
// 成员方法
static void show() {
System.out.println("haha");
}
}
需要注意的是:
- 在静态成员方法里是不能使用普通成员变量的
- 同理在普通成员方法里也是不能使用静态成员变量的
- 同样的静态成员方法里是不能使用普通成员方法的
因为静态成员变量和成员方法它们都是不依赖于对象的,普通成员变量和方法都是依赖于对象的,如果在静态里使用此时对象都还没有创建出来何来的成员呢?显然是不合理的。
但在普通成员方法里是可以使用普通成员变量的,因为想用普通成员方法,就得实例化对象。
只要被static所修饰的成员变量就在方法区
二、封装
在面向对象的语言里,封装是其特征之一。
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的,
把属性和动作隐藏,只提供相应的方法来调用即可,只要知道如何使用类就行了.
当类的实现者把内部的逻辑发生变化时,类的调用者根本不用因此而修改方法。
这样就降低了类使用者的学习和使用成本,从而降低了复杂程度,也保证了代码的安全性。
1. 构造方法
概念基本使用
构造方法是一种特殊方法,使用关键字new
示例化对象的时候会被自动调用,用于完成初始化操作
- 构造方法没有返回值,且方法名要和类名相同
- 构造方法是用来构造对象的(实例化)
new
的执行过程
- 为对象分配内存空间
- 调用合适的构造方法
每一个类都有一个默认的没有参数的构造方法,如果我们没有写构造方法,系统默认会有一个没有参数且没有任何内容的构造方法,比如下面这个代码就有一个默认的构造方法,当然如果你已经写了构造方法这个默认的就不存在了。
class Student {
//成员变量
public String name;
public int age;
}
构造方法的重载
方法重载的几个要求并不陌生,构造方法也是能重载的
- 方法名相同
- 参数类型和个数不相同
- 返回值不做要求
class Student {
//成员变量
public String name;
public int age;
public Student() {
System.out.println("无参构造方法");
}
public Student(String s) {
System.out.println("一个参数的构造方法");
}
public Student(String name, int age) {
System.out.println("两个参数的构造方法");
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("test");
Student s3 = new Student("test",20);
}
}
执行结果
无参构造方法
一个参数的构造方法
两个参数的构造方法
this关键字
this
关键字一共有三种用法
this.data
(访问当前对象的成员变量)this.func()
(访问当前对象的成员方法)this.()
(访问当前对象自己的构造方法)
有的书上说 this 代表当前对象,其实这样的说法并不准确。
正确的说法应该是:this表示当前对象引用,为什么这么说呢?
下面这段代码通过构造方法给成员变量赋值,而一个对象的创建要等构造方法执行完毕后才创建,所以this表示的是当前对象的引用
class Student {
//成员变量
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
this关键字也可以访问成员方法
class Student {
//成员变量
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void print() {
System.out.println("haha");
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student("张三",20);
s1.print();
}
}
this调用构造方法
注意:this调用构造方法一定要放在构造方法的第一行,每一个构造方法里只能使用一次
class Student {
//成员变量
public String name;
public int age;
public Student() {
this("haha");
System.out.println("无参构造方法");
}
public Student(String s) {
System.out.println("一个参数的构造方法");
}
public Student(String name, int age) {
System.out.println("两个参数的构造方法");
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student();
}
}
运行结果
一个参数的构造方法
无参构造方法
2. private
通过private
关键字实现封装.被private
修饰的成员变量和方法就只能在当前类中使用。在类外是无法直接访问的。和它对应的就是public
关键字,被public
修饰的成员变量和成员方法是在任何地方都是可以访问的。
成员变量被private
修饰后,一般要提供对应的get和set方法(根据需求),一个用来设置值一个用来获取值,这两个方法都是public
的。
这样外部就无法直接访问成员变量了,只能通过get和set方法来访问。
class Student {
//成员变量
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
}
构造方法也是可以被private
修饰的,在单例设计模式中会使用到。下面就是一个简单的饿汉单例
class Test {
private static Test test = new Test();
private Test() {
}
public static Test getExample() {
return test;
}
}
3. 封装的好处
- 提高了数据的安全性
别人不能够通过 变量名来修改某个私有的成员属性 - 操作简单
封装后,类的调用者在使用的时候,只需调用方法即可。 - 隐藏了实现
实现过程对类的调用者是不可见的,类的调用者只需调用方法即可,不知道具体实现
三、继承
1. 继承的概念
继承主要的意义就是为了代码的复用。
当我们定义的一些类存在了一些联系,简单来说就是多个类之间存在着大量重复的代码,为了消除代码的冗余就可以使用继承。
比如下面的代码,存在着大量代码的冗余,且这些类中存在着相同的方法和字段。
class Animal {
public String name;
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Dog {
public String name;
public void eat() {
System.out.println(this.name+"吃东西");
}
}
class Bird {
public String name;
public void eat() {
System.out.println(this.name+"吃东西");
}
public void fly() {
System.out.println(this.name+"起飞");
}
}
2. extends
在Java中通过extends
实现继承
class 子类 extends 父类 {
}
- Java中只支持单继承,一个子类只能继承一个父类
- 子类会继承父类的所有非
private
修饰的字段和方法 - 对于父类的
private
的字段和方法,子类是无法访问的,并不是没有继承而是被private
修饰只能在当前类中使用。 - 子类实例中包含着父类的实例,可以使用
super
关键字得到父类实例的引用
此时通过继承关系把上面的代码进行改造,让Dog类和Bird类继承Animal类。
class Animal {
public String name;
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Dog extends Animal {
}
class Bird extends Animal {
public void fly() {
System.out.println(this.name+"起飞");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Dog dog = new Dog();
Bird bird = new Bird();
dog.name = "二哈";
bird.name = "鸟";
dog.eat("饭");
bird.eat("饭");
}
}
运行结果
二哈正在吃饭
鸟正在吃饭
此时,像Animal这样被继承的类,我们称为父类、基类或者超类,对于像Cat和Bird这样
继承Animal的类,叫做子类或者派生类
就和现实中儿子继承父亲类似,子类会继承父类的字段和方法,从而达到代码复用的效果。
此时在内存中的存储
像成员方法和静态的成员变量都是存在方法区的,类里面的方法并不是是存在堆区的 ,方法本身是不占内存的,方法是存在方法区里的方法表中,在每个对象 new 好之后前面有几个字节存的就是方法表的地址,只有在调用这个方法时,会通过方法表的地址找到这个这个方法,然后在栈上为这个方法开辟栈帧。
3. super关键字
我们知道在一个类没有写任何构造方法的时候,默认会有意的没有参数且没有任何内容的构造方法。
下面这个例子,当我们给父类手动写上一个参数的构造方法时,继承关系下的子类都报错。
当子类继承父类后,在构造子类之前,必须先帮父类进行构造
这时候就用到了super
关键字。
super
表示父类实例的引用,和this
类似共有三种用法
-
super.父类成员变变量
-
super.父类成员方法
-
super()
调用父类的构造方法
注意:super和this一样不能在静态方法中使用
在用super关键字在子类的构造方法里帮父类构造的时候一定要在第一行
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name+"起飞");
}
}
如果父类和子类有了同名的成员变量?
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public String name = "鸟";
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(super.name);//使用父类的成员变量
super.eat("食物");//调用父类的成员方法
System.out.println(this.name+"起飞");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Bird bird = new Bird("乌鸦");
bird.fly();
}
}
运行结果
乌鸦
乌鸦正在吃食物
鸟起飞
此时的内存结构图
4. Object
如果一个类没有指定继承任何类的时候,那么这个类是默认 继承的就是Object类。也就是说Object
类是所有类的祖先。
比如随便写一个类就是默认继承Object类,它会继承Object类中的方法和字段。
class Animal {
public String name;
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
5. 更复杂的继承
继承可以继承多层,也就是子类还可以进一步的再派生出新的子类,虽然语法上支持,但不建议继承的层数超过3层。
当继承到第三层的时候,就应用使用final
修饰这个类,这样这个类就无法继承了。
class A {
String name;
}
class B extends A{
}
// 使用final修饰
final class C extends B {
}
6. final 关键字
- final修饰变量(常量,这个常量的值不能被修改)
- final修饰类,密封类(这个类不能再被继承)
- final修饰方法,密封方法(该方法不能进行重写)
7. 组和
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
例如表示一个学校:
public class Student {
}
public class Teacher {
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.
组合表示 has - a 语义 在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.
继承表示 is - a 语义 在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物
四、多态
1. 向向转型
概念
向上转型就是把一个子类对象给父类引用,也就是父类引用引用了子类的对象
class Animal {
public String name;
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public void fly() {
System.out.println(this.name+"起飞");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Animal animal = new Bird();//父类引用引用子类对象
}
}
我们把一个 Animal类型引用了它的子类Bird这就是向上转型
向向转型发生的时机
直接赋值
public static void main(String[] args) {
Animal animal = new Bird();//父类引用引用子类对象
}
方法传参
public class TestDemo2 {
public static void func(Animal animal) {
}
public static void main(String[] args) {
Bird bird = new Bird();
func(bird);
}
}
方法返回
public static Animal func() {
Bird bird = new Bird();
return bird;
}
注意事项
当发生向上转型的时候,通过父类引用只能调用父类自己的成员变量和方法
2. 向下转型
向下转型则是相反,拿一个子类的引用引用父类对象,前提是这个父类对象是通过向上转型得到的。
注意:向下转型的时候一定要进行强制类型转换
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name+"起飞");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Animal animal = new Bird("鸟");
Bird bird = (Bird)(animal);
bird.fly();
}
}
运行结果
鸟起飞
instanceof关键字
向下转型我们一般不建议使用,因为它非常不安全
下面这段代码让一个Cat对象发送向下转型,再让animall发生向下转型,显然这是不行的,一个Cat类型怎么能强制转换成Bird类型呢?运行救护发生类型转换异常
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name+"起飞");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Animal animal = new Cat("test");
Bird bird = (Bird)(animal);
bird.fly();
}
}
于是就可以使用instanceof
关键字
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较合适
public static void main(String[] args) {
Animal animal = new Cat("test");
if (animal instanceof Bird) {
Bird bird = (Bird)(animal);
bird.fly();
}
}
所以向下转型我们一般不建议使用,如果非要使用就一定要用instanceof 关键字判断一下。
3. 动态绑定(运行时绑定)
动态绑定发送的前提
- 先向上转型
- 通过父类引用来调用父类和子类中同名的覆盖方法(重写的方法)
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
System.out.println("Animal");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
public void eat(String food) {
System.out.println(this.name + "在吃" + food);
System.out.println("Cat");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Animal animal = new Cat("哈哈");
animal.eat("饭");
}
}
运行结果
哈哈在吃饭
Cat
我们发现这里我们通过父类引用调用了 Animal 和 Cat
同名的覆盖方法(重写),运行的是子类Cat的eat方法。此时这里就发生了动态绑定。
动态绑定也叫运行时绑定,其实在编译期间还是调用的父类的方法,在运行期间就才发生了绑定,绑定了子类的同名方法。
重写(@Override)
@Override
表示这个方法是重写的
重写的条件
- 方法名相同
- 方法的参数列表相同(参数个数和数据类型)
- 方法的返回值相同(也可以是父子关系)
返回值构成父子类关系也是可以发生重写的,此时叫做:协变类型
下面的代码的返回值就构成父子关系
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public Animal eat(String food) {
return null;
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public Cat eat(String food) {
return null;
}
}
需要注意的是:
- 子类的重写的这个方法,他的访问修饰符,一定要大于等于父类方法的访问修饰符
- 被final和static修饰的方法是不能发生重写的
动态绑定的一个坑
来看一段代码,我们实例化一个Cat类,因为Cat是子类,所以要帮父类先构造,那么在父类的构造方法里有一个 eat 方法,那么会执行哪个类里的 eat 方法呢?
class Animal {
public String name;
public Animal(String name) {
this.name = name;
this.eat();
}
public void eat() {
System.out.println("Animal");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println("Cat");
}
}
public class TestDemo2 {
public static void main(String[] args) {
Animal animal = new Cat("哈哈");
}
}
运行结果
Cat
我们发现这里调用的不是 是Animal 的 eat 方法,而是 Cat 的,因为这里也发生了动态绑定。
所以构造方法当中也是可以发生动态绑定的
注意:这样的代码以后不要轻易写出来!
4. 多态
多态其实就是一种思想,一个事物表现出不同的形态,就是多态。
假设我们要打印一些形状
class Shape {
public void draw() {
}
}
class Rect extends Shape{
public void draw() {
System.out.println("♦");
}
}
class Cycle extends Shape{
public void draw() {
System.out.println("●");
}
}
class Flower extends Shape{
public void draw() {
System.out.println("❀");
}
}
class Triangle extends Shape{
public void draw() {
System.out.println("△");
}
}
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
shape.draw();
Shape shape1 = new Cycle();
shape1.draw();
Shape shape2 = new Flower();
shape2.draw();
Shape shape3 = new Triangle();
shape3.draw();
}
}
运行结果
♦
●
❀
△
这不就是动态绑定吗?和多态有什么关系吗?
当我们在这个代码中添加一个drawMap方法后
public class TestDemo2 {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape shape = new Rect();
Shape shape1 = new Cycle();
Shape shape2 = new Flower();
Shape shape3 = new Triangle();
drawMap(shape);
drawMap(shape1);
drawMap(shape2);
drawMap(shape3);
}
}
运行结果
♦
●
❀
△
这不就是动态绑定吗?
我们细看会发现这是同样一个引用调用同样一个方法,能表现出不同的形态,这不就是多态思想?其实多态用到的就是动态绑定。
在这个代码中, 前面的代码是 类的实现者 编写的, TestDemo这个类的代码是 类的调用者 编写的.
当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当 前的shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape
对应的实例相关), 这种行为就称为 多态。
多态的好处
-
类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可 -
可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低
五、访问权限
1. 包(package)
`包时组织类的一种方式,使用包的主要目的是保证类的唯一性。
比如在合作开发的时候,在代码里如果出现两个TestDemo类,代码就会编译不通过。
导入包中的类
在Java中已经提供了很多现成的类供我们使用,比如
public static void main(String[] args) {
//随机数生成的类
java.util.Random random = new java.util.Random();
}
不过一般使用这种方式使用(除非多个包的类名冲突),而是通过import
关键字导入包
import java.util.Random;
public class TestDemo2 {
public static void main(String[] args) {
Random random = new Random();
}
}
还有一种将包下的类全部导入的写法
import java.util.*;
静态导入
使用import static
可以导入静态的方法和字段
import static java.lang.Math.*;
public class TestDemo2 {
public static void main(String[] args) {
int a = 100;
int b = 200;
System.out.println(max(a,b));
}
}
如果不使用静态导入
public class TestDemo2 {
public static void main(String[] args) {
int a = 100;
int b = 200;
System.out.println(Math.max(a,b));
}
}
将类放到包中
- 通过关键字
package
语句指定该代码在哪个包中 - 包名的规范一般用全小写字母
- 包名一般是公司的域名
- 注意:java.lang下的类是自动导入的
2. 4种访问权限
- 被
private
修饰的字段和方法只能在当前类中使用,类外是无法访问的。构造方法也可以是private
的 - 如果一个成员变量或者方法什么权限修饰符都不写,默认就是包访问权限,也就是在当前包中才能访问
- 刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.两全其美的办法就是
protected
关键字- 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
- 对于类的 子类 和 同一个包的其他类 来说, protected修饰的字段和方法是可以访问的
- 注意在不同包的子类是通过
super
关键字访问
- 被
public
修饰是在任何地方都可以访问到的
六、代码块
1. 本地代码块
方法内部的代码块
public static void main(String[] args) {
{
//本地代码块没有意义
}
}
2. 静态代码块
被static
修饰的代码块叫做静态代码块,
注意:静态代码块时在类加载的时候执行,整个程序中只会执行一次
public class TestDemo2 {
static {
System.out.println("这是一个静态代码块");
}
public static void main(String[] args) {
TestDemo2 testDemo2 = new TestDemo2();
System.out.println("===");
TestDemo2 testDemo3 = new TestDemo2();
}
}
运行结果
这是一个静态代码块
===
3. 实例代码块
实例代码块也叫构造代码块,在实例代码块中可以对属性进行赋值
class People{
private String name ;
private int age;//普通成员变量
private static int cont;//静态成员变量
{
this.name = "java";
this.age = 66;
cont = 100;
System.out.println("这是一个实例代码块");
}
}
4. 执行顺序
如果静态代码块、实例代码块和构造方法同时存在,它们的执行顺序是什么呢?
执行顺序为:
- 静态代码块
- 实例代码块
- 构造方法
class Person {
String name;
public Person() {
System.out.println("Person构造方法");
}
static {
System.out.println("静态代码块,Person");
}
{
System.out.println("实例代码块,Person");
}
}
class Student extends Person {
public Student() {
System.out.println("Student构造方法");
}
static {
System.out.println("静态代码块,Student");
}
{
System.out.println("实例代码块,Student");
}
}
public class TestDemo {
public static void main(String[] args) {
Person person = new Student();
}
}
运行结果
静态代码块,Person
静态代码块,Student
实例代码块,Person
Person构造方法
实例代码块,Student
Student构造方法
- 静态代码块只会执行一次
- 如果出现继承关系:由父及子,静态先行。
七、抽象类
1.抽象类的定义
-
包含抽象方法的类,就叫做抽象类
-
如果一个方法没有具体的实现,就可以使用
abstract
来修饰,且包含抽象方法的类也一定要用abstract
来修饰abstract class Shape { abstract public void draw(); }
2. 抽象类的注意事项
1.抽象类是不能被实例化的(也就是不能new
的)
2.在抽象类中可以包含抽象方法也可以包含非抽象方法
abstract class Shape {
abstract public void draw();
public void print() {
System.out.println("普通方法");
}
}
3.当一个普通的类,继承了一个抽象类,那么普通这个普通类一定要重写抽象类当中的抽象方法,或者用abstract
修饰这个子类,让它也变成抽象类。
abstract class Shape {
abstract public void draw();
public void print() {
System.out.println("普通方法");
}
}
class Circle extends Shape {
@Override
public void draw() {
}
}
用abstract
修饰子类
abstract class Shape {
abstract public void draw();
public void print() {
System.out.println("普通方法");
}
}
abstract class Circle extends Shape {
}
4.当一个普通类继承了一个上述继承一个抽象类且没有重写抽象方法,如果它自己也带以一个抽象类,那么此时这个普通类就得重写这两个类的抽象方法了。
abstract class Shape {
abstract public void draw();
public void print() {
System.out.println("普通方法");
}
}
abstract class Circle extends Shape {
abstract public void circleInit();
}
class Son extends Circle {
@Override
public void draw() {
}
@Override
public void circleInit() {
}
}
5.抽象类不能被final
修饰,因为抽象类本来就用来继承的,所以不能被final
修饰
6.抽象方法是为了重写的,也不能被final
修饰,且抽象方法不能用private
修饰
7.父类(抽象类)的普通方法,在类外也可以被调用,但是需要让父类引用去引用子类对象(向上转型),或者在子类中通过super
关键字访问父类(抽象类)的成员变量或者成员方法。
abstract class Shape {
abstract public void draw();
public void print() {
System.out.println("抽象类的普通方法");
}
}
class Circle extends Shape {
@Override
public void draw() {
}
}
public class TestDemo2 {
public static void main(String[] test) {
Shape shape = new Circle();
shape.print();
}
}
运行结果
抽象类的普通方法
3. 抽象类的意义
-
抽象类存在的最大意义就是为了被继承
-
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法
-
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
-
比如前面多态的代码就可以改成抽象类
八、接口
1. 接口的定义
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含 静态常量
在Java中使用interface
定义一个接口
interface IShape {
}
2. 接口中的方法和成员变量
- 接口中的方法默认都是抽象方法
public abstract
- 接口中的成员变量默认都是公开的静态常量
public static final
interface IShape {
public static final int ID = 100;
abstract public void test();
}
注意:
1.接口中的方法前面的修饰符写或不写,默认都是 public abstract 也就是抽象方法
2.接口当中的成员变量,也是一样,默认就是 public static final
3. default关键字
接口中的方法都是没有具体实现的,但是从jdk1.8开始接口中的方法是可以有具体实现的,但这个方法必须被default
修饰
interface IShape {
public static final int ID = 100;
abstract public void test();
default void prt() {
System.out.println("test");
}
}
4. 接口的使用
- 接口是不可以被实例化的
- 接口的使用可以通过
implements
实现接口 - 接口也是可以发送向上转型和动态绑定的
interface IShape {
void draw();
}
class Rect implements IShape{
public void draw() {
System.out.println("♦");
}
}
class Cycle implements IShape{
public void draw() {
System.out.println("●");
}
}
class Flower implements IShape{
public void draw() {
System.out.println("❀");
}
}
class Triangle implements IShape{
public void draw() {
System.out.println("△");
}
}
public class TestDemo2 {
public static void draw(IShape iShape) {
iShape.draw();
}
public static void main(String[] test) {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
Triangle triangle = new Triangle();
draw(rect);
draw(cycle);
draw(flower);
draw(triangle);
}
}
运行结果
♦
●
❀
△
5.实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果。
- 一个类可以实现多个接口,通过关键字 implements
- 注意:如果实现了多个接口,一定要重写每个接口里面的方法
interface A {
void prt();
}
interface B {
void test();
}
class Test implements A,B {
@Override
public void prt() {
}
@Override
public void test() {
}
}
注意事项:如果两个接口,方法名相同,此时在类当中,只需要重写一个方法就好了,但最终调用的就是这一个方法。但并不建议这么做,接口都不一样就不要写同样的方法了。
interface A {
void test();
}
interface B {
void test();
}
class Test implements A,B {
@Override
public void test() {
System.out.println("test");
}
}
public class TestDemo2 {
public static void main(String[] test) {
A a = new Test();
B b = new Test();
a.test();
b.test();
}
}
运行结果
test
test
6. 接口的拓展
一个类可以拓展多个接口,通过关键字 extends 来拓展多个接口,也就说可以让一个接口具备另外几个接口的功能。
注意: 一但有一个普通类实现了这拓展的接口,那么这个类就要重写这个接口的抽像方法了
interface a {
void a();
}
interface b {
void b();
}
interface C extends a,b {
void c();
}
class MyClass implements C {
@Override
public void a() {
}
@Override
public void b() {
}
@Override
public void c() {
}
}
7. 接口使用示例
我们定义了一个 Animal 类来描述动物,三个接口,分别代表 飞、跑、游。再定义了一个Cat类继承了Animal 类,并且实现了 跑 这个接口
interface a {
void a();
}
interface b {
void b();
}
interface C extends a,b {
void c();
}
class MyClass implements C {
@Override
public void a() {
}
@Override
public void b() {
}
@Override
public void c() {
}
}
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
interface IFly {
void fly();
}
interface IRun {
void run();
}
interface ISwimming {
void swim();
}
class Cat extends Animal implements IRun {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name+"猫在跑");
}
}
class Bird extends Animal implements IFly,IRun {
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+"鸟在飞");
}
@Override
public void run() {
System.out.println(this.name+"鸟在跑");
}
}
class Duck extends Animal implements IRun,IFly,ISwimming {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+"鸭子飞");
}
@Override
public void run() {
System.out.println(this.name+"鸭子跑");
}
@Override
public void swim() {
System.out.println(this.name+"鸭子游泳");
}
}
public class TestDemo2 {
public static void fly(IFly iFly) {
iFly.fly();
}
public static void run(IRun iRun) {
iRun.run();
}
public static void swimming(ISwimming iSwimming) {
iSwimming.swim();
}
public static void main(String[] test) {
Cat cat = new Cat("猫");
Bird bird = new Bird("鸟");
Duck duck = new Duck("鸭子");
run(cat);
run(bird);
run(duck);
fly(bird);
fly(duck);
swimming(duck);
}
}
运行结果
猫猫在跑
鸟鸟在跑
鸭子鸭子跑
鸟鸟在飞
鸭子鸭子飞
鸭子鸭子游泳
我们发现接口是非常灵活的,我们不用管传过去的是什么,只要传过去的对象只要实现了对应的接口,都可以调用这个方法,发生向下转型和动态绑定。
8. 抽象类和接口的区别
成员变量
- 抽象类中成员变量和普通类没有区别
- 接口中的成员变量默认都是
public static final
成员方法
- 抽象类中可以包含普通方法,也可以包含抽象方法
- 接口的方法默认都是抽象方法(
abstract
) - 在jdk1.8中
default
修饰的方法可以有具体实现
实现方式
- 抽象类是不能被实例化的,所以只能被子类使用
extend
关键字继承,且一个类只能继承一个父类 - 接口也是不能被实例化,只能使用
implements
来实现,同时一个类可以实现多个接口,一个接口可以使用extends
扩展多个接口的功能