赞
踩
标识符就是用于给 Java 程序中变量、类、方法等命名的符号。
标识符要遵守以下规则:
1. 变量
在JAVA中我们通过三个元素来描述变量:变量类型,变量名以及变量值。
变量类型 变量名 值(其中String具有不可变性,重新赋值后会生成新的String对象,love变量名这实际是指向对象地址的引用,"imooc"为具体的值)。
love=“I love imooc”; 变量重新赋值,重新指向了一个新的对象,对象值为"i love imooc“。
String love = "imooc";
变量类型 变量名 值(其中String具有不可变性,重新赋值后会生成新的String对象,love变量名这实际是指向对象地址的引用,"imooc"为具体的值)。
love=“I love imooc”; 变量重新赋值,重新指向了一个新的对象,对象值为"i love imooc“。
2. 变量使用规则
3. 自动类型转换
4. 强制类型转换
5. 常量的应用
理解:常量可以被理解为一种特殊的变量,值被设定后,在程序运行过程中不能改变
创建方式:
final double PI = 3.14;
命名方式:常量名一般为大写
好处:可以提高代码的可维护性
1. 基本数据类型
注意事项:
2. 引用数据类型
字符串、数组、类、接口、Lambda
https://blog.csdn.net/shuaigexiaobo/article/details/88535745
https://blog.csdn.net/heyjj1226/article/details/49334395/
方法、数组见文件(java基础)
关键字都是小写的,共53个。
1. 保留关键字
序号 | 关键字 | 作用 |
---|---|---|
1 | const | 常量,常数:用于修改字段或局部变量的声明。 |
2 | goto | 转到:指定跳转到标签,找到标签后,程序将处理从下一行开始的命令 |
2. 访问修饰符的关键字
序号 | 关键字 | 作用 |
---|---|---|
1 | public (公有的) | 可跨包 |
2 | protected (受保护的) | 当前包内可用 |
3 | private (私有的) | 当前类可用 |
3. 定义类、接口、抽象类和实现接口、继承类的关键字、实例化对象
序号 | 关键字 | 作用 |
---|---|---|
1 | class (类) | public class A(){}花括号里是已实现的方法体,类名需要与文件名相同 |
2 | interface (接口) | public interface B(){}花括号里有方法体,但没有实现,方法体句子后面是英文分号;结尾 |
3 | abstract (声明抽象) | public abstract class C(){}介于类与接口中间,可以有,也可以没有已经实现的方法体 |
4 | implemenst (实现) | 用于类或接口,实现接口public class A interface B(){} |
5 | extends (继承) | 用于类继承类public class A extends D(){} |
6 | new (创建新对象) | A a=new A();A表示一个类 |
4. 包的关键字
序号 | 关键字 | 作用 |
---|---|---|
1 | import (导包的关键字) | 当使用某个包的一些类时,仅需要类名,然后使用ctrl+shift+o或者选定类名(类或属性或方法)按住ctrl+单击,即可自动插入类所在的包 |
2 | package (定义包的关键字) | 将所有相关的类放在一个包类以便查找修改等 |
5. 数据类型关键字
序号 | 关键字 | 作用 |
---|---|---|
1 | byte (字节型) | 8bit |
2 | short (短整型) | 16bit |
3 | int (整型) | 32bit |
4 | long (长整型) | 64bit |
5 | float (浮点型) | 32bit |
6 | double (双精度) | 64bit |
7 | char (字节型) | public void A(){}其他需要反回的经常与return连用 |
8 | boolean (布尔型) | - |
9 | void (无返回) | - |
10 | null (空值) | - |
11 | true (真) | - |
12 | false (假) | - |
6. 条件循环(流程控制)
序号 | 关键字 | 作用 |
---|---|---|
1 | if (如果) | if(条件语句{执行代码} |
2 | else (否则,或者) | 常与if连用,用法相同:if(…){…}else{…} |
3 | while (当什么时候) | while(条件语句){执行代码} |
4 | for(满足三个条件时) | for(初始化循环变量;判断条件;循环变量值){} |
5 | switch (选择结构) | switch(表达式){case 常量表达式1:语句1;…case 常量表达式2;语句2;default:语句;}default就是如果没有匹配的case就执行它,default并不是必须的。case后的语句可以不用大括号。 |
6 | case (匹配switch的表达式里的结果) | 同上 |
7 | default (默认) | default就是如果没有匹配的case就执行它, default并不是必须的 |
8 | do (运行) | 通长与while连用 |
9 | break (跳出循环) | 直接跳出循环,执行循环体后的代码 |
10 | continue (继续) | 中断本次循环,并开始下一轮循环 |
11 | return (返回) | return 一个返回值类型 |
12 | instanceof(实例) | 一个二元操作符,和==、>、<是同一类的。测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据 |
7. 修饰方法、类、属性和变量
序号 | 关键字 | 作用 |
---|---|---|
1 | static(静态的) | 属性和方法都可以用static修饰,直接使用类名、属性和方法名。只有内部类可以使用static关键字修饰,调用直接使用类名、内部类类名进行调用。static可以独立存在 |
2 | final(最终的不可被改变) | 方法和类都可用final来修饰;final修饰的类是不能被继承的;final修饰的方法是不能被子类重写。常量的定义:final修饰的属性就是常量 |
3 | super(调用父类的方法) | 常见public void paint(Graphics g){super.paint(g);…} |
4 | this(当前类的父类的对象) | 调用当前类中的方法(表示调用这个方法的对象)this.addActionListener(al):等等 |
5 | native(本地) | - |
6 | strictfp(严格,精准) | - |
7 | synchronized(线程,同步) | 一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块 |
8 | transient(短暂) | - |
9 | volatile(易失) | - |
8. 错误处理
序号 | 关键字 | 作用 |
---|---|---|
1 | catch(处理异常) | (1)try+catch 程序流程是:运行到try块中,如果有异常抛出,则转到catch块去处理。然后执行catch块后面的语句(2)try+catch+finally 程序流程是:运行到try块中,如果有异常抛出,则转到catch垮,catch块执行完毕后,执行finally块的代码,再执行finally块后面的代码。如果没有异常抛出,执行完try块,也要去执行finally块的代码。然后执行finally块后 面的语句 (3)try+finally 程序流程是:运行到try块中,如果有异常抛出,则转到finally块的代码。 |
2 | try(捕获异常) | - |
3 | finally(有没有异常都执行) | - |
4 | throw(抛出一个异常对象) | 一些可以导致程序出问题,比如书写错误,逻辑错误或者是api的应用错误等等。为力防止程序的崩溃就要预先检测这些因素,所以java使用了异常这个机制。在java中异常是靠“抛出” 也就是英语的“throw”来使用的,意思是如果发现到什么异常的时候就把错误信息“抛出”。 |
5 | throws(声明一个异常可能被抛出) | 把异常交给他的上级管理,自己不进行异常处理 |
9. 其它
序号 | 关键字 | 作用 |
---|---|---|
1 | enum(枚举,列举,型别) | - |
2 | assert(断言) | - |
面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。
但是在面向对象设计之前,广泛采用的是面向过程,面向过程只是针对于自己来解决问题。面向过程的操作是以程序的基本功能实现为主,实现之后就完成了,也不考虑修改的可能性,面向对象,更多的是要进行子模块化的设计,每一个模块都需要单独存在,并且可以被重复利用,所以,面向对象的开发更像是一个具备标准的开发模式。
在面向对象定义之中,也规定了一些基本的特征:
封装:保护内部的操作不被破坏;
继承:在原本的基础之上继续进行扩充;
多态:在一个指定的范围之内进行概念的转换。
对于面向对象的开发来讲也分为三个过程:OOA(面向对象分析)、OOD(面向对象设计)、OOP(面向对象编程)。
类与对象时整个面向对象中最基础的组成单元。
类:是抽象的概念集合,表示的是一个共性的产物,类之中定义的是属性和行为(方法);
对象:对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同对象。
可以一句话来总结出类和对象的区别:类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。
类与对象的定义和使用
在Java中定义类,使用关键字class完成。语法如下:
class 类名称 {
属性 (变量) ;
行为 (方法) ;
}
范例:定义一个Person类
class Person { // 类名称首字母大写
String name ;
int age ;
public void tell() { // 没有static
System.out.println("姓名:" + name + ",年龄:" + age) ;
}
}
类定义完成之后,肯定无法直接使用。如果要使用,必须依靠对象,那么由于类属于引用数据类型,所以对象的产生格式(两种格式)如下:
(1)格式一:声明并实例化对象
类名称 对象名称 = new 类名称() ;
(2)格式二:先声明对象,然后实例化对象:
类名称 对象名称 = null ;
对象名称 = new 类名称();
引用数据类型与基本数据类型最大的不同在于:引用数据类型需要内存的分配和使用。所以,关键字new的主要功能就是分配内存空间,也就是说,只要使用引用数据类型,就要使用关键字new来分配内存空间。
当一个实例化对象产生之后,可以按照如下的方式进行类的操作:
对象.属性:表示调用类之中的属性;
对象.方法():表示调用类之中的方法。
范例:使用对象操作类
package com.wz.classandobj; class Person { String name ; int age ; public void get() { System.out.println("姓名:" + name + ",年龄:" + age); } } public class TestDemo { public static void main(String args[]) { Person per = new Person() ;// 声明并实例化对象 per.name = "张三" ;//操作属性内容 per.age = 30 ;//操作属性内容 per.get() ;//调用类中的get()方法 } }
以上完成了一个类和对象的操作关系,下面换另外一个操作来观察一下:
package com.wz.classandobj; class Person { String name ; int age ; public void get() { System.out.println("姓名:" + name + ",年龄:" + age); } } public class TestDemo { public static void main(String args[]) { Person per = null;//声明对象 per = new Person() ;//实例化对象 per.name = "张三" ;//操作属性内容 per.age = 30 ;//操作属性内容 per.get() ;//调用类中的get()方法 } }
实例化方式对比
我们从内存的角度分析。首先,给出两种内存空间的概念:
(1)堆内存:保存对象的属性内容。堆内存需要用new关键字来分配空间;
(2)栈内存:保存的是堆内存的地址(在这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)。
任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。
于是,上面两种对象实例化对象方式内存表示如下:
两种方式的区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。
引用传递的精髓:同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。
下面通过若干个程序,以及程序的内存分配图,来进行代码的讲解。
我们来看一个范例:
class Person { String name ; int age ; public void tell() { System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class TestDemo { public static void main(String args[]) { Person per1 = new Person() ; // 声明并实例化对象 per1.name = "张三" ; per1.age = 20 ; Person per2 = per1 ; // 引用传递 per2.name = "李四" ; per1.tell() ; } }
class Person { String name ; int age ; public void tell() { System.out.println("姓名:" + name + ",年龄:" + age) ; } } public class TestDemo { public static void main(String args[]) { Person per1 = new Person() ; // 声明并实例化对象 Person per2 = new Person() ; per1.name = "张三" ; per2.name = "李四" ; per2.age = 30 ; per2 = per1 ;// 引用传递 per2.name = "王五" ; per1.tell() ; } }
垃圾:指的是在程序开发之中没有任何对象所指向的一块堆内存空间,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)不定期的进行回收与空间的释放。
区别:定义变量位置的不同
成员变量:存在于堆内存中,和类一起创建
局部变量:存在于栈内存中,当方法执行完成,让出内存,让其他方法来使用内存
成员变量
类变量:从该类的准备阶段起开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同;
实例变量:从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例变量的作用域与对应实例的生存范围相同。
正是基于这个原因,可以把类变量和实例变量统称为成员变量。
其中类变量可以理解为类成员变量,它作为类本身的一个成员,与类本身共存亡;
实例变量则可以理解为实例成员变量,它作为实例的一个成员与实例共存亡。
只要类存在,类就可以访问类变量 类.类变量
只要实例存在,实例就可以访问实例变量 实例.实例变量
当然实例也可以访问类变量。但是需要注意的是因为实例不拥有类变量,所以通过实例来访问类变量进行操作,实际上是对类变量进行操作,当有其他实例来访问类变量时,访问的类变量是被对象访问操作过的类变量。
成员变量无需显示初始化,只要为一个类定义了类变量或实例变量,系统就会在这个类的准备阶段或创建该类的实例时进行默认初始化。
形参:在定义方法签名时定义的变量,形参的作用域在整个方法中都有效
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效
代码块局部变量:这个局部变量的作用域从定义该变量的地方生效,到该代码结束时失效。
一个变量只在一对{}中起作用。
java允许局部变量和成员变量同名,如果方法中局部变量和成员变量同名,局部变量就会覆盖成员变量,如果需要在这个方法中引用被覆盖成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定访问成员变量。
public class Person {
public int num;
public String name;
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.num = 2;
p2.num = 3;
p1.name = "张三";
p2.name = "李四";
}
}
Person p1 = new Person();
如果这行代码是第一次使用Person类,则系统通常会在第一次使用Person类时加载这个类,并初始化这个类,在类的准备 阶段,系统将会为该类的类变量分配内存空间,并指定默认初始值。当person类初始化完成后,系统内存中的存储示意图如下图所示。
当person类初始化完成后,系统将在堆内存中为Person分配一块内存空间,实际上是创建了一个类对象,在这块内存区里包含了保存num类变量的内存,并设置num的默认初始值为0。
系统接着创建了一个Person对象,并把这个Person对象赋给p1变量,Person对象包含了名为name的实例变量,实例变量是在创建实例时分配内存空间并指定初始值的。当创建了第一个person对象后,系统内存中的存储示意图如下图所示。
从上图可以看出num不属于对象,它属于类,所以创建第一个对象时并不需要为num分配内存空间,系统只是为name分配了内存空间,并指定初始值为null。
Person p2 = new Person();
创建第二个对象p2时,由于在创建第一个对象时已经对类进行了初始化,所以在创建p2时对类进行初始化,对象的创建过程与第一个对象的创建过程没有什么区别。
第二个对象创建完成后,成员变量如上图所示在内存中存储。
当程序需要访问类变量时,尽量使用类来作为主调,不要使用对象作为主调,这个可以避免产生歧义。
*局部变量必须经过显示初始化之后才能使用,系统不会为局部变量执行初始化。定义了局部变量以后,系统并没有给局部变量进行初始化,直到程序给这个局部变量赋给初值时,系统才会为这个局部变量分配内存空间,并将初始值保存到这块内存中。
*局部变量不属于任何类或者实例,因此它总是保存在方法的栈内存中。如果局部变量是基本数据类型,则该变量直接存储在方法的栈内存中,如果是引用变量则将引用的地址存储在方法的栈内存中。
*栈内存中的变量无需系统垃圾回收,随着方法或者代码块的运行结束而结束。局部变量通常只保存了具体的值或者引用地址,所以所占的内存比较小。
能不使用成员变量就别使用成员变量
能不使用方法局部变量就别使用方法局部变量
使用代码块局部变量性能最好。
匿名对象:没有名字的对象
正常:
Car c = new Car();
c.run();
匿名:
new Car().run();
使用方法
public static void show(Car c)
{
//......
}
show(new Car());
匿名对象的内存分析
new Car().num = 5;
new Car().color = "green";
new Car().run();
概念:将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的访问和操作。
目的:直接通过操控类对象来达到目的,不需要对具体实现十分了解,使类属性和方法的具体实现对外不可见。不但方便还起到了保护作用。
java的访问修饰符
访问修饰符 | 本类 | 同包 | 子类 | 其它 |
---|---|---|---|---|
private | √ | |||
(default) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
1. 功能概述:调用成员变量、成员方法、构造方法
成员变量:
public class Student{
String name;
private void SetName(String name){
this.name = name;
}
}
this其实是当前类对象的引用,通过当前这个类实例化的对象的引用来访问对象的成员变量。
注意:
this关键字访问类的成员变量和成员函数时不受访问权限的控制,可以访问本类中所有的成员变量和方法,包括private的成员变量和方法。也可以通过this访问本类的static成员,不过由于static成员可以通过类名直接访问,如果通过this来访问会有“The static field ××× should be accessed in a static way”的警告信息。不能在类的static成员或static块中使用this。
成员方法:
public class ThisTest { String name; private void setName(String name) { this.name = name; System.out.println("setName: " + this.name); } private void thisTest(String name) { this.setName(name); System.out.println("thisTest: " + this.name); } public static void main(String[] args) { ThisTest tt = new ThisTest(); tt.setName("Yida"); tt.thisTest("Jenny"); } }
这段代码中,首先创建了Test类,该类与之前的没什么区别,只是增加了一个成员方法thisTest(),在这个成员方法中,通过this关键字调用了之前的成员函数setName(),程序运行结果如下所示:
setName: Yida
setName: Jenny
thisTest: Jenny
构造方法:
可以在一个构造方法中通过this关键字调用其它构造方法
约束:
只能在构造方法中通过this关键字来调用其它构造方法,普通方法中不能调用
不能通过this关键字递归调用构造方法,即不能在一个构造方法中直接或间接调用改构造方法本身
通过this调用调用其它构造方法时,必须放在构造方法的第一行执行
由于super调用父类的构造方法也必须在构造方法的第一行执行,因此,通过 this 和 super 调用构造方法不可能在同一个构造方法中执行
public class Student { //定义一个类,类的名字为student。
public Student() { //定义一个方法,名字与类相同故为构造方法
this(“Hello!”);
}
public Student(String name) { //定义一个带形式参数的构造方法
}
}
返回对象的值
this关键字除了可以引用变量或者成员方法之外,还有一个重大的作用就是返回类的引用。如在代码中,可以使用return this,来返回某个类的引用。此时这个this关键字就代表类的名称。如代码在上面student类中,那么代码代表的含义就是return student。可见,这个this关键字除了可以引用变量或者成员方法之外,还可以作为类的返回值,这才是this关键字最引人注意的地方。
1. 什么是构造方法:也叫构造器、构造函数,构造器在每个项目中几乎无处不在。当 new 对象时,就会调用构造器。构造器格式如下:
[修饰符,比如public] 类名 (参数列表,可以没有参数){
//这里不能有return
}
2. 默认构造器:如果没有定义构造器,则默认创建一个无参构造器
public class People {
}
3. 如何禁止对象被外部创建:直降将构造器的修饰符设置为private
public class People {
private People(){
}
}
4. 构造器重载:一个对象允许创建多个构造器,通过不同的参数列表实现重载。
public class People {
//通过new People()调用
public People(){
}
//通过new People("字符串") 调用
public People(String str){
}
}
5. 构造器继承:子类构造器默认继承父类的默认构造器。
若父类没有默认构造器,则可以通过super指定继承。
/**
* 父类构造器
*/
public class SuperClass {
/**
* 自定义带参构造器
*/
public SuperClass(String str){
System.out.println("父类的带参构造方法,参数为:" + str);
}
}
/** * 子类构造器 */ public class SubClass extends SuperClass { /** * 无参构造器 */ public SubClass(){ //由于SuperClass没有无参构造器,所以必须在子类构造器中通过 super("字符串")来调用,否则编译器会报错。 //如果没定义该句,则编译器会默认调用 super() super(""); } /** * 带参构造器 */ public SubClass(String subStr){ //由于SuperClass没有无参构造器,所以必须在子类构造器中通过 super("字符串")来调用,否则编译器会报错。 //如果没定义该句,则编译器会默认调用 super() super(subStr); } }
final 类不允许被继承,编译器直接报错。原因在于 final 修饰符修饰后是不能被修改的,但是子类继承父类之后可以修改,两者冲突,因此 final 类不允许被继承。
6. 构造器、静态代码块、构造代码块的执行顺序:
无继承情况下的执行顺序:
静态代码块:只在程序启动后执行一次,优先级最高
构造代码块:任何一个构造器被调用的时候,都会先执行构造代码块,优先级低于静态代码块
构造器:优先级低于构造代码块
优先级:静态代码块 > 构造代码块 > 构造器
public class People { static { System.out.println("静态代码块,程序启动后执行,只会执行一次"); } /** * 默认的无参构造器 */ public People(){ System.out.println("默认构造器"); } /** * 构造器重载,自定义一个带参构造器 * @param str */ public People(String str){ System.out.println("带参构造器,参数:" + str); } { System.out.println("构造代码块,每次调用构造方法都会执行一次"); } }
实例化People
public static void main(String[] args){
System.out.println("--------------people----------------");
People people = new People();
System.out.println("--------------people1----------------");
People people1 = new People("张三");
}
--------------people----------------
静态代码块,程序启动后执行,只会执行一次
构造代码块,每次调用构造方法都会执行一次
默认构造器
--------------people1----------------
构造代码块,每次调用构造方法都会执行一次
带参构造器,参数:张三
有继承情况下的执行顺序
父类静态代码块:只在程序启动后执行一次,优先级最高
子类静态代码块:只在程序启动后执行一次,优先级低于父类静态代码块
父类构造代码块:父类任何一个构造器被调用的时候,都会执行一次,优先级低于子类静态代码块
父类构造器:优先级低于父类构造代码
子类构造代码块:子类任何一个构造器被调用的时候,都会执行一次,优先级低于父类构造器
子类构造器:优先级低于子类构造代码块
优先级:父类静态代码块 > 子类静态代码块 > 父类构造代码块 > 父类构造器 > 子类构造代码块 > 子类构造器
定义父类SuperClass
/** * 父类构造器 */ public class SuperClass { { System.out.println("父类构造代码块,每次调用构造方法都会执行的"); } /** * 父类无参构造方法 */ public SuperClass(){ System.out.println("父类的默认构造方法"); } /** * 重载,自定义父类带参构造方法 * @param str */ public SuperClass(String str){ System.out.println("父类的带参构造方法,参数为:" + str); } static { System.out.println("父类的静态代码块,程序启动后执行,只会执行一次"); } }
定义子类SubClass,继承SuperClass
/** * 子类构造器,继承SuperClass */ public class SubClass extends SuperClass { static { System.out.println("子类的静态代码块,程序启动后执行,只会执行一次,先执行父类的,再执行子类的"); } { System.out.println("子类构造代码块,每次调用构造方法都会执行的"); } /** * 无参构造器 */ public SubClass(){ //这里没有指定调用父类哪个构造器,会默认调用 super(),调用父类的无参构造器public SuperClass() } /** * 重载构造器,多传两个参数 * @param str * @param str1 */ public SubClass(String str,String str1){ //必须写在构造器第一行,调用父类构造器 public SuperClass(String str) super(str); System.out.println("子类带参构造器:" + str1); } }
实例化SubClass
public static void main(String[] args){
System.out.println("--------------subClass1----------------");
SubClass subClass1 = new SubClass();
System.out.println("--------------subClass2----------------");
SubClass subClass2 = new SubClass("子类第一个参数","子类第二个参数");
}
--------------subClass1----------------
父类的静态代码块,程序启动后执行,只会执行一次
子类的静态代码块,程序启动后执行,只会执行一次,先执行父类的,再执行子类的
父类构造代码块,每次调用构造方法都会执行的
父类的默认构造方法
子类构造代码块,每次调用构造方法都会执行的
--------------subClass2----------------
父类构造代码块,每次调用构造方法都会执行的
父类的带参构造方法,参数为:子类第一个参数
子类构造代码块,每次调用构造方法都会执行的
子类带参构造器:子类第二个参数
1. 概念:继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
2. 语法:关键字 extends 表明正在构造的新类派生于一个已存在的类。
已存在的类被称为超类(super class)、基类(base class)或父类(parent class);
新类被称为子类(subclass)、派生类(derived class)或孩子类(child class)。
在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放到超类中,而将具有特色用途的方法放在子类中,这种将通用的功能放到超类的做法,在面向对象程序设计中十分普遍。
super 关键字有两个用途:一是调用超类的方法,二是调用超类的构造器。super 不是一个对象的引用,不能将 super 赋给另一个对象变量,它只是一个指示编译器调用超类方法的特有关键字。
3. 初始化过程:
public class Animal { private static int A = printInit("static Animal region init"); public Animal() { System.out.println("--Animal--"); } public static int printInit(String s) { System.out.println(s); return 30; } } public class Bird extends Animal{ private static int B = Animal.printInit("static Bird region init"); public Bird() { System.out.println("--Bird--"); } } public class Parrot extends Bird{ private static int P = Animal.printInit("static Parrot region init"); public Parrot() { System.out.println("--Parrot--"); } public static void main(String[] arg) { Parrot parrot = new Parrot(); } }
static Animal region init
static Bird region init
static Parrot region init
--Animal--
--Bird--
--Parrot--
接下来看看 Parrot、Bird、Animal 类构造器字节码:
/** Parrot **/ public com.mdj.test.Parrot(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method com/mdj/test/Bird."<init>":()V 4: return /** Bird **/ public com.mdj.test.Bird(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method com/mdj/test/Animal."<init>":()V 4: return /** Animal **/ public com.mdj.test.Animal(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return
当创建 Parrot 对象实例时,会生成构造函数链 Parrot -> Bird -> Animal -> Object,最后才执行 Parrot 构造函数初始化:
从运行结果可以看出,根基类的 static 会首先执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的 static 初始化可能会依赖于基类成员能否被正确初始化。
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。
这样做是有意义的,因为构造器具有一项特殊的任务:**检查对象是否被正确的构造。导出类只能访问它自己的成员,不能访问基类的成员(基类成员通常是 private)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此必须令所有的构造器都得到调用,否则就不可能正确构造完整对象。**这正是编译器为什么要强制每个导出类都必须调用构造器的原因。在构造器内部,我们必须确保所要使用的成员都已经构建完毕。为确保这一目的,唯一的办法就是首先调用基类构造器。
4. 分类
根据继承的特性可以分为纯继承与扩展。
纯继承:纯粹的“is-a”(是一种)关系,因为一个类的接口已经确定了它应该是什么。继承可以确保所有的导出类具有基类的接口,且绝对不会少。基类可以接收发送给导出类的任何消息,因为二者有着完全相同的接口。
扩展:“is-like-a”(像一个)关系,导出类就像是一个基类——它有着相同基类的基本接口,但是它还具有由额外方法实现的其他特性。导出类中接口扩展部分不能被基类访问,因此,一旦向上转型,就不能调用那些新方法。
5. 特性
单继承,不允许一个类继承多个父类。(区分:内部类实现的多继承)
继承最重要的方面是用来表现新类和基类之间的关系。这种关系可以用“新类是现有类的一种类型”。
继承可以把新类向上转换成基类,这是向上转型的一种表现。
由导出类转型成基类,在继承图上是向上移动的。
向上转型是从一个较专用类型向较通用类型转换。
导出类是基类的一个超集。它可能比基类含有更多的方法,但它必须至少具备基类中所有的方法。
6. 优缺点
优点:
缺点:
7. 扩展
当创建了一个导出类的一个对象时,这个子对象和你直接用基类创建的对象是一样的。二者区别在于,后者来至于外部,而基类的子对象被包装在导出类对象内部。那是不是真的会在导出类的内部再 new 一个父类的对象?
public class Widget {
public synchronized void doSomething() {}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
super.doSomething();
}
public static void main(String[] args){
Widget widget = new LoggingWidget();
widget.doSomething();
}
}
LoggingWidget.class 字节码:
LoggingWidget.class 字节码:
Constant pool: #1 = Methodref #5.#15 // com/java/widget/Widget."<init>":()V #2 = Methodref #5.#16 // com/java/widget/Widget.doSomething:()V #3 = Class #17 // com/java/widget/LoggingWidget #4 = Methodref #3.#15 // com/java/widget/LoggingWidget."<init>":()V #5 = Class #18 // com/java/widget/Widget #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 doSomething #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 LoggingWidget.java #15 = NameAndType #6:#7 // "<init>":()V #16 = NameAndType #10:#7 // doSomething:()V #17 = Utf8 com/java/widget/LoggingWidget #18 = Utf8 com/java/widget/Widget { public com.java.widget.LoggingWidget(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method com/java/widget/Widget."<init>":()V 4: return LineNumberTable: line 9: 0 public synchronized void doSomething(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #2 // Method com/java/widget/Widget.doSomething:()V 4: return LineNumberTable: line 11: 0 line 13: 4 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #3 // class com/java/widget/LoggingWidget 3: dup 4: invokespecial #4 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #2 // Method com/java/widget/Widget.doSomething:()V 12: return LineNumberTable: line 16: 0 line 17: 8 line 18: 12 } SourceFile: "LoggingWidget.java"
从字节码可知:new 一个 LoggingWidget 对象时,在 LoggingWidget 构造函数中会调用 Widget 的 实例构造器,正确的初始化父类的状态变量。实际上只是调用父类的实例构造器,不是在子类对象上 new 一个父类对象。
从 Java 程序的视角来看,对象创建才刚刚开始 —— 方法还没有执行,所有的字段都还为零。所以,一般来说(由字节码中是否跟随 invokespecial 指令所决定),执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
从以上可知:在创建子类对象时,并非在内部也创建一个父类对象,只是调用父类的实例构造器来正确的初始化对应的父类状态。
8. 组合与继承
继承和组合都能从现有类型生成新类型,组合一般是将现有类型作为新类型的底层实现的一部分加以复用,而继承复用的是接口。
组合在开发过程中常使用的手段,显示的在新类中放置子对象。组合的语法:只需将对象引用置于新类中即可。组合技术通常用于想在新类中使用现有类的功能而非它的接口的情景。在新类中嵌入某个对象,让其实现所需的功能,但用户看到的只是新类所定义的接口,而非所嵌入对象的接口。
public class TestClass {
private Animal mAnimal;
}
TestClass.class 字节码:
public com.java.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
尽管面向对象编程对继承极力强调,但在开始一个设计时,一般应优先选择使用组合,只有在确实必要时才使用继承,同时组合更具灵活性。
继承涉及到基类和导出类这两个类,而不是只有一个类,但从外部看来,它就像是一个和基类具有相同接口的新类,或许还会有额外的方法和域。但继承并不只是复制基类的接口。当创建了一个导出类的一个对象时,这个子对象和你直接用基类创建的对象是一样的。二者区别在于,后者来至于外部,而基类的子对象被包装在导出类对象内部。
继承与组合应选择哪个?一个最清晰的判断方法:是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必须的,如果不需要,则应当好好考虑。是否需要继承,只要记得自问一下“我真的需要向上转型吗?”就能较好的在这两种技术中作出决定。组合是一种强耦合关系,你和我都有共同的生命期。
https://blog.csdn.net/dilixinxixitong2009/article/details/77962030
封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
继承是为了重用父类代码。两个类若存在IS-A的关系就可以使用继承,同时继承也为实现多态做了铺垫。
多态:父类引用变量指向子类对象,因此前提是必须有父、子类关系
定义:父类类型 变量名 = new 子类类型();
**多态成员变量:**编译、运行均看左边
Fu f = new Zi();
System.out.println(f.num);// num 为成员变量,因此取出来的值为 Fu 中的 num 值
**多态成员方法:**编译看左边,运行看右边
Fu f = new Zi();
// 虽然表面看起来是调用 Fu 中的 fun(),但是实际上调用的是 Zi中重写后的方法
System.out.println(f.fun());
作用:判断某个对象是否属于某种数据类型
返回值:true \ false
Fu f1=new Zi();
Fu f2=new Son();
if(f1 instanceof Zi){
System.out.println("f1是Zi的类型");
}
else{
System.out.println("f1是Son的类型");
}
**向上转型:**多态本身就是向上转型
使用格式:父类类型 变量名 = new 子类类型();
使用场景:当不需要面对子类类型时,提高程序的扩展性,或使用父类的功能就可以完成相应的操作。
向下转型:
使用格式:子类类型 变量名 = (子类类型) 父类类型的变量;
使用场景:当需要使用特有的功能时。
public class demo04 { public static void main(String[] args) { People p=new Stu(); p.eat(); //调用特有的方法 Stu s=(Stu)p; s.study(); //((Stu) p).study(); } } class People{ public void eat(){ System.out.println("吃饭"); } } class Stu extends People{ @Override public void eat(){ System.out.println("吃水煮肉片"); } public void study(){ System.out.println("好好学习"); } } class Teachers extends People{ @Override public void eat(){ System.out.println("吃樱桃"); } public void teach(){ System.out.println("认真授课"); } }
吃水煮肉片
好好学习
public class demo1 { public static void main(String[] args) { A a=new A(); a.show(); B b=new B(); b.show(); } } class A{ public void show(){ show2(); } public void show2(){ System.out.println("A"); } } class B extends A{ public void show2(){ System.out.println("B"); } } class C extends B{ public void show(){ super.show(); } public void show2(){ System.out.println("C"); } }
A
B
https://www.cnblogs.com/chenssy/p/3372798.html
普通类是一个完善的功能类,可以直接产生实例化对象,并且在普通类中可以包含有构造方法、普通方法、static方法、常量和变量等内容。而抽象类是指在普通类的结构里面增加抽象方法的组成部分。
如果一个方法使用 abstract 来修饰,则说明该方法是抽象方法,抽象方法只有声明没有实现。需要注意的是 abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。
抽象类无法被实例化,即没有方法体
抽象方法必须存在于抽象类中
子类重写父类时,必须重写父类所有的抽象方法
注意:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。
抽象类和抽象方法都要使用 abstract 关键字声明。
如果一个方法被声明为抽象的,那么这个类也必须声明为抽象的。而一个抽象类中,可以有 0~n 个抽象方法,以及 0~n 个具体方法。
抽象类不能实例化,也就是不能使用 new 关键字创建对象。
接口继承和实现继承的规则不同,一个类只有一个直接父类,但可以实现多个接口。
Java 接口的方法只能是抽象的和公开的,Java 接口不能有构造方法,Java 接口可以有 public、static 和 final 属性。
[public] interface interface_name [extends interface1_name[, interface2_name,…]]
{
//接口体,其中可以包含定义常量和声明方法
[public] [static] [final] type constant_name=value; //定义常量
[public] [abstract] returnType method_name(parameter_list); //声明方法
}
具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口(default),其访问将局限于所属的包。
方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
接口没有构造方法,不能被实例化。
接口被定义后,一个或者多个类都可以实现该接口,这需要在实现接口的类的定义中包含 implements 子句,然后实现由接口定义的方法。实现接口的一般形式如下:
<public> class <class_name> [extends superclass_name] [implements interface[, interface…]] {
//主体
}
内部类基础
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
(1)成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
注意:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
class Circle { private double radius = 0; public Circle(double radius) { this.radius = radius; getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问 } private Draw getDrawInstance() { return new Draw(); } class Draw { //内部类 public void drawSahpe() { System.out.println(radius); //外部类的private成员 } } }
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
public class Test { public static void main(String[] args) { //第一种方式: Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建 //第二种方式: Outter.Inner inner1 = outter.getInnerInstance(); } } class Outter { private Inner inner = null; public Outter() { } public Inner getInnerInstance() { if(inner == null) inner = new Inner(); return inner; } class Inner { public Inner() { } } }
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
(2)局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class People{ public People() { } } class Man{ public Man(){ } public People getWoman(){ class Woman extends People{ //局部内部类 int age =0; } return new Woman(); } }
(3)匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。下面这段代码是一段Android事件监听代码:
同样的,匿名内部类也是不能有访问修饰符和static修饰符的。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
(4)静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
public class Test { public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } } class Outter { public Outter() { } static class Inner { public Inner() { } } }
https://www.cnblogs.com/dolphin0520/p/3811445.html
https://blog.csdn.net/feiyanaffection/article/details/81394745
https://blog.csdn.net/zhangqunshuai/article/details/80660974
List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
public interface List<E> extends Collection<E>
public interface Set<E> extends Collection<E>
public interface Map<K,V>
重要的实现类
List:
ArrayList
LinkedList
Vector
Set:
HashSet
LinkedHashSet
TreeSet
Map:
HashMap
TreeMap
LinkedHashMap
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。