目录
一、包(package)
包,即package,其实就是存放了很多类。
存着就有要用的时候,所以我们可以用import来引入类
这里带来一个注意点,import 是引入类,不是引入包。
例如util是一个包,但是Date是一个类
第一种引入包就报错了,第二种引入类不报错
再看这种方式
这个就是导入util包中所有的类,但只有你用到包中某个类时,才会自动引用。
创建包
首先要注意包名要小写
我们右击src
在写包名,我们可以用 '.'来表示下一级目录
如果这样输入:
就会如下创建好文件夹
import与package的区别
上面说过,import是导入包中的类,所以区别就来了
package是为了保证类的唯一性,将类导入到包中,供以后使用
import导入包类后,可以区别两个同名的但不同包的类。
这里就引出了类的唯一性,举个例子:
我这里有一个TestDemo的java文件,我还想创建一个同名的类,可以在包中实现。
这两个是同名但不同包的类,在调用时只要说明包即可
包的访问权限控制
前面学过public和private,但有时也不加
如果在包中的类不写public或者private,比如上面包中的Test
public class Test {
int val = 10;
}
我在包外的TestDemo中是无法调用它的。
val标红,说明语法错误,不能访问其他包中不带public的变量或方法。
二、继承
概念
继承,即对共性的抽取,来看例子:
class Dog{
public String name;
public int age;
public void eat(){
System.out.println(name + "eat");
}
}
class Bird{
public String wing;
public String name;
public int age;
public void fly(){
System.out.println(name + "fly");
}
public void eat(){
System.out.println(name + "eat");
}
}
看这两个类,都有共同的name、age变量和eat()方法
所以我们可以对共性抽取,写一个Animal类,包含这三个元素
再让dog类和bird类继承(extends),就可以包含这三个元素,使代码更简洁
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(name + "eat");
}
}
class Dog extends Animal{
}
class Bird extends Animal{
public String wing;
public void fly(){
System.out.println(name + "fly");
}
}
这样就完成了继承,Animal就是父类,Dog和Bird就是子类
我们来实例化dog类,看看有没有继承元素。
public class Main{
public static void main1(String[] args) {
Dog dog = new Dog("hh", 18);
dog.name = "hh";
System.out.println(dog.name);
dog.eat();
}
}
运行结果:
继承注意点:
1. 父类中被 private、static 修饰的方法与变量不能被继承
2. 一个类如果被 final 修饰也不能被继承
3. 一个子类只能继承一个父类,Java不能多继承
super关键字
从上面的dog类可以看出,dog类继承了animal类的所有元素
但是如果我想用animal的变量呢?
这里引出super关键字,用这个关键字就可以调用父类的方法或变量。
这里我们把父类animal的eat方法区别开来,再修改一下dog类
class Animal{
public String name;
public int age;
public void eat(){
System.out.println("ani::eat");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog::eat");
}
public void test(){
eat();
super.eat();
}
}
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.test();
}
}
来看结果:
这样super的作用就体现了。
接下来我们看super调用构造方法
我们在父类animal中写一个带参的构造方法
这里引出注意点:
任何一个子类构造方法第一句都会先访问父类无参构造方法
如果父类有带参构造,子类必须帮助父类实现构造方法
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(name + "ani::eat");
}
public Animal(String name, int age){
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
public Dog(String name, int age) {
//这里只帮父类初始化了,子类自己没初始化
super(name, age);
}
public void eat(){
System.out.println(name + " dog::eat");
}
public void test(){
eat();
super.eat();
}
}
这是我们调用一下:
public class test {
public static void main(String[] args) {
Dog dog = new Dog("haha", 18);
dog.test();
}
}
从结果可以看出,父类animal初识化了,但是子类并没有。
这个haha是父类的name,父类、子类共用这个haha。
如果我们给子类一个同名的变量name
class Dog extends Animal{
public String name;//和父类同名的变量
public Dog(String name, int age) {
super(name, age);
}
public void eat(){
System.out.println(name + " dog::eat");
}
public void test(){
eat();
super.eat();
}
}
此时再给这个name赋个值
public class test {
public static void main(String[] args) {
Dog dog = new Dog("haha", 18);
dog.name = "jj";
dog.test();
}
}
来运行一下:
这里发现dog的name已经被改了。
由此得出:在子类和父类同名变量时,子类中的变量优先,除非使用super
来加个super试试,把dog类的eat中name加上super
public void eat(){
System.out.println(super.name + " dog::eat");
}
由此可以发现,通过super,又重新调用了父类的name。
三、protected关键字
我们了解,private 能将数据封装起来,但是子类不能访问
但是用 public 不能达到封装的效果。
所以就有了 protected 关键字,它的效果夹在他们中间
对于类的调用者来说, protected 修饰的字段和方法是不能访问的
对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
来个例子:
对于子类来说:
class Animal{
//...
protected int a = 0;
//...
}
class Dog extends Animal{
//...
public void test(){
System.out.println(super.a);
}
}
这样就可以访问。
对于不同包:
//包的默认访问权限——只能在当前包中使用
public class Test {
int val = 10;//其他包外的子类不能访问
protected int count = 99;//其他包外的子类可以访问
}
class Test2 extends Test {//继承了别的包的类
public static void main(String[] args) {
Test2 test2 = new Test2();
//System.out.println(test2.val);错误
System.out.println(test2.count);//但是protected的count就可以
}
}
结果如上。
来个总结:
四、多态
向上转型
我们实例化一个dog类,也可以通过animal的引用指向dog类的对象
//向上转型,即父类引用 引用子类对象
Animal animal = new Dog("haha", 19);
向上转型也可以作为函数参数:
public static void func(Animal animal) {
}
public static void main(String[] args) {
//第二种情况,用函数
Dog dog = new Dog("hehe",20);
func(dog);
}
向上转型也可以用作返回值的情况:
public static Animal func2(){
Dog dog = new Dog("hehe",20);
return dog;
}
public static void main(String[] args) {
//第三种情况,返回值
func2();
}
动态绑定
当父类与子类中出现同名的方法会怎么样呢?
class Animal{
public String name;
public void eat(){
System.out.println(name + " ani::eat");
}
public Animal(String name){
this.name = name;
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
public void eat(){
System.out.println(name + " dog::eat");
}
}
public class test {
public static void main(String[] args) {
Animal animal = new Dog("11");
animal.eat();
}
}
我们可以发现,虽然通过父类的引用,但是调用了子类的方法。
因此在 Java 中,调用某个类的方法,究竟执行了父类还是子类的代码
要看究竟这个引用指向的是父类对象还是子类对象.
这个过程是程序运行时决定的(而不是编译期), 因此称为动态绑定.
方法重写(override)
上面的 eat 方法,就是典型的方法重写。
重写即子类重新把父类的方法写一遍。
这里再整理一下重写规则:
1. 重写和重载完全不一样. 不要混淆
重载,在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数个数/类型
重写,在子类中创建,参数列表、返回类型必须与被重写的方法相同
2. 普通方法可以重写, private、static、final 修饰的静态方法不能重写
3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
比如这种就不符合规定:
class Animal{
protected void eat(){
System.out.println(name + " ani::eat");
}
}
class Dog extends Animal{
private void eat(){
System.out.println(name + " dog::eat");
}
}
4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
这里还有一个注意点:
class Animal{
public String name = "heihei";
}
class Dog extends Animal{
public String name = "haha";
}
public class test {
public static void main(String[] args) {
Animal animal1 = new Dog();
System.out.println(animal1.name);
}
}
这样的结果如何呢?
animal的类型毕竟是Animal类型,父类引用只能访问自己父类成员和方法。
来总结一波:
向上转型,但自己还是父类类型,只能引用自己的方法与变量。
但如果有重写方法,优先用子类重写的方法。
在构造方法中调用重写的方法
class Animal{
public String name;
public void eat(){
System.out.println(name + "ani::eat");
}
public Animal(String name, int age){
eat();
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
public Dog(String name){
super(name);
}
@Override
public void eat(){
System.out.println(name + "狼吞虎咽");
}
}
public class Main{
public static void main(String[] args) {
Dog dog = new Dog("haha");
}
}
这里dog实例化,会先调用父类的构造方法
父类的构造方法内有父类自己的eat方法, 所以这里的eat会用哪个呢?
结果是子类的,这里也发生了动态绑定。
向下转型
有向上,那也有向下,
我们再来一个 Bird 类来看例子:
class Bird extends Animal{
public void fly(){
System.out.println(name + "fly");
}
public Bird(String name, int age){
super(name, age);
}
}
public class Main{
public static void main(String[] args) {
//向下转型
Animal animal = new Bird("haha", 20);
Bird bird = (Bird) animal;
}
}
把animal对象转成bird对象,就是向下转型
但是又有特殊情况:如果当时创建的是Dog类的对象,就没有fly的方法:
public static void main(String[] args) {
Animal animal = new Dog("haha");
Bird bird = (Bird) animal;
bird.fly();
}
这里new了一个Dog,我们向下转型再调用fly方法就会报错。
所以要判断一下之前的对象,用instanceof:
public static void main(String[] args) {
//向下转型
Animal animal = new Dog("haha");
Bird bird = (Bird) animal;
//不是所有动物都会飞,要判断
if (bird instanceof Bird){
bird.fly();
}
}
码字不易,只求大佬一赞👍。欢迎大家指正,一起加油学习。🎉