前言
目录内容
第一部分:编程语言核心结构
主要知识点:变量、基本语法、分支、循环、数组、…
第二部分:Java面向对象的核心逻辑
主要知识点:OOP、封装、继承、多态、接口、…
第三部分:开发Java SE高级应用程序
主要知识点:异常、集合、I/O、多线程、反射机制、网络编程、……
JDK和JRE
JDK(Java Development Kit Java开发工具包)
JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了
JRE。所以安装了JDK,就不用在单独安装JRE了。
其中的开发工具:编译工具(javac.exe) 打包工具(jar.exe)等
JRE(Java Runtime Environment Java运行环境)
包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,
如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
//编译文件
javac hello_world.java,生成helloWorld.class
//运行文件
java.exe helloWorld.class
注释
除了单行和多行注释,还有文档注释
/**
@author
@version
*/
javadoc -d myHello -author -version HelloJava
数据类型转换
包含自动类型转换和强制类型转换
class hello {
public static void main(String[] args){
char c1 = 'a';
int i3 = 10;
int i4 = c1 + i3;
System.out.println(i4);
}
}
//107
class hello{
public static void main(String[] args){
double a1 = 12.9;
int a3;
a3 = (int)a1;
System.out.println(a3);
}
}
这里左移2位就是原有的再乘2^2,如果是右移3位就是除2^3
三元运算符: (m > n)? m : n
输入
import java.util.Scanner;
class hello{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int num = scan.nextInt(); //这里nextInt表示输入的是int类型,其他还有如下
System.out.println(num);
}
}
[
分支结构
if-else
public class AgeTest{
public static void main(String args[]){
int age = 75;
if (age< 0) {
System.out.println("不可能!");
} else if (age>250) {
System.out.println("是个妖怪!");
} else {
System.out.println(“人家芳龄 " + age +" ,马马乎乎啦!");
} } }
switch-case
public class SwitchTest {
public static void main(String args[]) {
int i = 1;
switch (i) {
case 0:
System.out.println("zero");
break;
case 1:
System.out.println("one");
break;
default:
System.out.println("default");
break; } }
循环结构
for
public class ForLoop {
public static void main(String args[]) {
int result = 0;
for (int i = 1; i <= 100; i++) {
result += i; }
System.out.println("result=" + result);
} }
while
public class WhileLoop {
public static void main(String args[]) {
int result = 0;
int i = 1;
while (i <= 100) {
result += i; i++;
}
System.out.println("result=" + result);
} }
do-while
public class DoWhileLoop {
public static void main(String args[]) {
int result = 0, i = 1;
do {
result += i; i++;
} while (i <= 100);
System.out.println("result=" + result);
} }
continue label和break label
我们知道break是跳出一层循环,而break label可以指定跳出到那层循环,比如
数组
* ① 一维数组的声明和初始化
* ② 如何调用数组的指定位置的元素
* ③ 如何获取数组的长度
* ④ 如何遍历数组
* ⑤ 数组元素的默认初始化值 :见ArrayTest1.java
* ⑥ 数组的内存解析 :见ArrayTest1.java
1.一维数组的声明和初始化
int[] arr = new int[]{1,2,3,4,5,6};
int[] arr = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
2. 如何调用数组的指定位置的元素
//动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行
int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;
//静态初始化:在定义数组的同时就为数组元素分配空间并赋值。
int arr[] = new int[]{3, 9, 8};
int[] arr = {3,9,8};
3.如何获取数组的长度
每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长度(元素个数)
class hello_world{
public static void main(String[] args) {
int[] a = new int[]{1,2,3,4,5,6,7};
System.out.println(a.length);
}
}
4.如何遍历数组
System.out.println(names[0]);
System.out.println(names[1]);
System.out.println(names[2]);
System.out.println(names[3]);
--------------------------------------
for(int i = 0;i < names.length;i++){
System.out.println(names[i]);
}
5.数组元素的默认初始化值
* > 数组元素是整型:0
* > 数组元素是浮点型:0.0
* > 数组元素是char型:0或'\u0000',而非'0'
* > 数组元素是boolean型:false
*
* > 数组元素是引用数据类型:null
数组元素类型 | 元素默认初始值 |
---|---|
byte | 0 |
short | 0 |
in | 0 |
long | 0L |
float | 0.0F |
double | 0 |
char | 0 或写为:’\u0000’(表现为空) |
boolean | false |
引用类型 | NULL |
public class Test{
public static void main(String args[]){
int[] s;
s = new int[10];
for ( int i=0; i<10; i++ ) {
s[i] =2*i+1;
System.out.println(s[i]);
} } }
public class ArrayTest {
public static void main(String[] args) {
int[] arr = new int[]{8,2,1,0,3};
int[] index = new int[]{2,0,3,2,4,0,1,3,2,3,3};
String tel = "";
for(int i = 0;i < index.length;i++){
tel += arr[index[i]];
System.out.println("联系方式:" + tel);
二维数组
* ① 二维数组的声明和初始化
* ② 如何调用数组的指定位置的元素
* ③ 如何获取数组的长度
* ④ 如何遍历数组
* ⑤ 数组元素的默认初始化值 :见 ArrayTest3.java
* ⑥ 数组的内存解析 :见 ArrayTest3.java
1.二维数组的声明和初始化
int[][] arr1 = new int[3][];
arr1[0] = {1,2};
arr1[1] = {2,3};
arr1[2] = {3,4};
2.如何调用数组的指定位置的元素
//2.如何调用数组的指定位置的元素
System.out.println(arr1[0][1]);//2
System.out.println(arr2[1][1]);//null
3.如何获取数组的长度
//3.获取数组的长度
System.out.println(arr4.length);//3
System.out.println(arr4[0].length);//3
System.out.println(arr4[1].length);//4
4.如何遍历数组
//4.如何遍历二维数组
for(int i = 0;i < arr4.length;i++){
for(int j = 0;j < arr4[i].length;j++){
System.out.print(arr4[i][j] + " ");
}
System.out.println();
}
5.数组元素的默认初始化值
* 针对于初始化方式一:比如:int[][] arr = new int[4][3];
* 外层元素的初始化值为:地址值
* 内层元素的初始化值为:与一维数组初始化情况相同
* 针对于初始化方式二:比如:int[][] arr = new int[4][];
* 外层元素的初始化值为:null
* 内层元素的初始化值为:不能调用,否则报错。
Arrays工具类的使用
1 boolean equals(int[] a,int[] b) 判断两个数组是否相等。
2 String toString(int[] a) 输出数组信息。
3 void fill(int[] a,int val) 将指定值填充到数组之中。
4 void sort(int[] a) 对数组进行排序。
5 int binarySearch(int[] a,int key) 对排序后的数组进行二分法检索指定的值。
java.util.Arrays类的sort()方法提供了数组元素排序功能:
import java.util.Arrays;
public class SortTest {
public static void main(String[] args) {
int [] numbers = {5,900,1,5,77,30,64,700};
Arrays.sort(numbers);
for(int i = 0; i < numbers.length; i++){
System.out.println(numbers[i]); } } }
面向对象
// Java面向对象学习的三条主线
1.Java类及类的成员:属性、方法、构造器;代码块、内部类
2.面向对象的三大特征:封装性、继承性、多态性、(抽象性)
3.其它关键字:this、super、static、final、abstract、interface、package、import等
Java类及类的成员
class Person{
//属性
String name;
int age;
boolean isMale;
//方法
public void eat(){
System.out.println("can eat!!!");
}
public void talk(String language){
System.out.println("can speak, the language is " + " " + language);
}
}
class hello_world{
public static void main(String[] args) {
Person lizhiyuan = new Person();
lizhiyuan.name = "batmanfuture";
lizhiyuan.age = 20;
lizhiyuan.isMale = true;
System.out.println(lizhiyuan.name);
System.out.println(lizhiyuan.age + " 和 " + lizhiyuan.isMale);
lizhiyuan.eat();
lizhiyuan.talk("chinese");
}
}
* public void eat(){}
* public void sleep(int hour){}
* public String getName(){}
* public String getNation(String nation){}
这里如果没有返回值就是void,有返回值就是对应返回的数据类型;再就是有无形参,位置形参和关键字形参
2.1 在类中声明的位置的不同
* 属性:直接定义在类的一对{}内
* 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
*
* 2.2 关于权限修饰符的不同
* 属性:可以在声明属性时,指明其权限,使用权限修饰符。
* 常用的权限修饰符:private、public、缺省、protected --->封装性
* 目前,大家声明属性时,都使用缺省就可以了。
* 局部变量:不可以使用权限修饰符。
*
* 2.3 默认初始化值的情况:
* 属性:类的属性,根据其类型,都有默认初始化值。
* 整型(byte、short、int、long):0
* 浮点型(float、double):0.0
* 字符型(char):0 (或'\u0000')
* 布尔型(boolean):false
*
* 引用数据类型(类、数组、接口):null
练习
public class Person {
String name;
int age;
int sex;
public void study(){
System.out.println("studying");
}
public void showAge(){
System.out.println(age);
}
public int addAge(int i){
i = i + 2;
return i;
}
}
public class Main {
public static void main(String[] args) {
Student[] stus = new Student[20];
for (int i=0; i < stus.length;i++){
stus[i] = new Student();
stus[i].number += 1;
stus[i].score += 1;
stus[i].state += 1;
}
for (int i =0 ;i < stus.length;i++){
System.out.println(stus[i].number + " " + stus[i].score + " " + stus[i].state);
}
}
}
class Student {
// 学号number(int),年级state(int),成绩
//score(int)
int number;
int state;
int score;
}
本类中的public
本类中的default
本类中的protected
本类中的private
public class Main {
public static void main(String[] args) {
Student[] stus = new Student[20];
for (int i = 0;i < stus.length; i++){
stus[i] = new Student();
stus[i].number = i + 1;
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1);
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
for (int i = 0;i < stus.length; i++){
System.out.println(stus[i]);
}
}
}
class Student{
int number;
int state;
int score;
}
一个源文件中只能有一个 public 类
一个源文件可以有多个非 public 类
源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。
如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。
如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
匿名对象
// 直接调用,不需要实例化
new Phone().sendEmail();
new Phone().playGame();
public class Main {
public static void main(String[] args) {
new Student().test();
new Student().showPrice(100);
}
}
class Student{
void test(){
System.out.println("123");
}
void showPrice(int haha){
System.out.println(haha);
}
}
方法的重载
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
特点:与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个小数的和
double add(double x,double y){return x+y;}
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
int a = p1.add(1,2);
System.out.println(a);
int b = p1.add(1,2,3);
System.out.println(b);
double c = p1.add(1.2,2.3);
System.out.println(c);
}
int add(int x,int y){
return x+y;
}
int add(int x,int y,int z){
return x+y+z;
}
double add(double x,double y){
return x+y;
}
}
练习
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
p1.mOL(10);//100
p1.mOL("batmanfuture");
p1.mOL(10,5);//50
}
void mOL(int x){
System.out.println(x * x);
}
void mOL(int x, int y){
System.out.println(x * y);
}
void mOL(String x){
System.out.println(x);
}
}
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
p1.max(1,2);
p1.max(1.2,1.3);
p1.max(1.1,1.2,1.3);
}
void max(int x, int y){
if (x > y){
System.out.println(x);
}else{
System.out.println(y);
}
}
void max(double x, double y){
if (x > y){
System.out.println(x);
}else{
System.out.println(y);
}
}
void max(double x,double y,double z){
double max1 = x;
if (y > max1){
max1 = y;
}
if (z > max1){
max1 = z;
}
System.out.println(max1);
}
}
可变个数的形参
JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量,允许直接定义能和多个实参相匹配的形参
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
p1.show("batmanfuture","123456");
}
public void show(String ... strs){
System.out.println(strs[0]);
System.out.println(strs[1]);
System.out.println("123");
}
}
// public static void test(int a ,String…books);
public class Main{
public static void main(String[] args) {
Main p1 = new Main();
p1.show(12);
p1.show("haha");
p1.show("batman","future");
}
public void show(int i){
System.out.println("1");
}
public void show(String s){
System.out.println("2");
}
public void show(String ... strs){
System.out.println("3");
}
}
// 但是当我们的第二个String没有时,这里String ... strs就可以用来接收任意数量的字符串,如下
public class Main{
public static void main(String[] args) {
Main p1 = new Main();
p1.show();
p1.show("haha");
p1.show("batman","future");
}
public void show(String ... strs){
System.out.println("3");
}
}
值传递
值传递机制:
* 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
* 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
java和python不同
练习
// 方法一
public class Main {
public static void main(String[] args) {
int a = 10;
int b = 10;
method(a,b);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
//代码
public static void method(int a,int b){
a = a * 10;
b = b * 20;
System.out.println(a);
System.out.println(b);
System.exit(0);
}
}
public class Main {
public static void main(String[] args) {
Circle p1 = new Circle();
double w = p1.findArea(2);
System.out.println(w);
}
}
//代码
class Circle{
double radius;
double findArea(double haha){
return haha * haha * 3.14;
}
}
public class Main {
public static void main(String[] args) {
Circle p1 = new Circle();
PassObject p2 = new PassObject();
p2.printAreas(p1,5);
}
}
//代码
class Circle{
double radius;
double findArea(double haha){
return haha * haha * 3.14;
}
}
class PassObject{
public void printAreas(Circle c, int time){
for (int i=0; i < time;i++){
c.radius = i;
System.out.println(c.radius + "\t\t" + c.findArea(c.radius));
}
}
}
递归
这里不是用for循环解决问题,而是通过递归的方式
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
int w = p1.getSum(10);
System.out.println(w);
}
public int getSum(int n){
if (n == 1){
return 1;
}else{
return n + getSum(n - 1);
}
}
}
练习
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
int w = p1.getSum(3); // 这里的 3 就是我们传入的值
System.out.println(w);
}
public int getSum(int n){
if (n == 0){
return 1;
}else if (n == 1){
return 4;
}else{
// f(n+2)=2*f(n+1) + f(n)
return 2 * getSum(n - 1) + getSum(n - 2);
}
}
}
public class Main {
public static void main(String[] args) {
Main p1 = new Main();
int w = p1.getSum(10);
System.out.println(w);
}
public int getSum(int n){
if (n == 1){
return 1;
}else if (n == 2){
return 1;
}else{
// f(n+2)=2*f(n+1) + f(n)
return getSum(n - 1) + getSum(n - 2);
}
}
}
封装性
// 属性是私有的(private),并不能直接实例化对象调用,而是通过我们的get和set方法间接去调用
权限访问控制修饰符 : default, public , protected, private
非访问控制修饰符 : final, abstract, static, synchronized
4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public(不然你都调用不了这个类何谈去去实例化再调用对象的方法呢)
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
[
// PersonTest.java
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(100);
int a = p1.getAge();
System.out.println(a);
}
}
// Test.java
class Person {
int age;
int getAge(){
return age;
}
void setAge(int age1){
age = age1;
}
}
构造器
如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
定义构造器的格式:权限修饰符 类名(形参列表){}
一个类中定义的多个构造器,彼此构成重载
一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
一个类中,至少会有一个构造器
// 构造器的作用:创建对象;给对象进行初始化
public class Main {
public static void main(String[] args) {
User p1 = new User(2);
System.out.println(p1.age);
}
}
class User{
String name;
int age = 1;
public User(int a){
age = a;
}
}
---------------------------------------------
输出:2
赋值的位置
① 默认初始化 比如int的默认值是0
② 显式初始化 比如直接赋值int a = 2;
③ 构造器中初始化 构造器中传值,初始化赋值
④ 通过“对象.属性“或“对象.方法”的方式赋值 比如p1.getName(),p1.setName()
⑤ 在代码块中赋值
执行的先后顺序:① - ② / ⑤ - ③ - ④
Java Bean
JavaBean是一种Java语言写成的可重用组件
// 类是公共的
// 有一个无参的公共的构造器
// 有属性,且有对应的get、set方法
public class JavaBean {
private String name; // 属性一般定义为private
private int age;
public JavaBean() {
}
public int getAge() {
return age; }
public void setAge(int a) {
age = a; }
public String getName() {
return name; }
public void setName(String n) {
name = n; } }
UML类图
this调用属性和方法
// 我们可以用this来区分属性和局部变量 this.属性
* 3. this调用构造器
* ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
* ② 构造器中不能通过"this(形参列表)"方式调用自己
* ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
* ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
* ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ; }
public void getInfo(){
System.out.println("姓名:" + name) ;
this.speak();
}
public void speak(){
System.out.println("年龄:" + this.age);
} }
this调用构造器
public class PersonTest {
public static void main(String[] args) {
Student stu = new Student("batmanfuture",20);
}
}
class Student{
String name;
int age;
public Student(){
System.out.println("空参构造器");
}
public Student(int age){
this();
System.out.println("一个参数构造器");
this.age = age;
}
public Student(String name,int age){
this(age);
System.out.println("两个参数构造器");
this.name = name;
this.age = age;
}
}
练习
// Main.java
public class Main {
public static void main(String[] args) {
Boy boy = new Boy("batmanfuture", 24);
boy.shout();
Girl girl = new Girl("gxy",23);
girl.marry(boy);
Girl girl1 = new Girl("lfd",24);
int w = girl.compare(girl1);
System.out.println(w);
}
}
// Boy.java
public class Boy {
String name;
int age;
public Boy(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void marry(Girl girl){
System.out.println("我想娶" + girl.name);
}
public void shout(){
if (age > 22){
System.out.println("你可以娶了");
}else{
System.out.println("你还小");
}
}
}
// Girl.java
public class Girl {
String name;
int age;
public Girl(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void marry(Boy boy){
System.out.println("我想嫁给" + boy.getName());
boy.marry(this);
}
public int compare(Girl girl){
if (this.age > girl.age){
return 1;
}else if (this.age < girl.age){
return -1;
}else{
return 0;
}
}
}
package和import
一、package关键字的使用
使用package声明类或接口所属的包,声明在源文件的首行
每"."一次,就代表一层文件目录。 com.batmanfuture.gitbook
同一个包下,不能命名同名的接口,类;反之不同的包下,可以命名相同的接口和类
* 二、import关键字的使用
在源文件中显式的使用import结构导入指定包下的类、接口
声明在包的声明和类的声明之间
如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
import static:导入指定类或接口中的静态结构:属性或方法。
继承性
Java中关于继承性的规定:
1.一个类可以被多个子类继承。
2.Java中类的单继承性:一个类只能有一个父类
3.子父类是相对的概念。
4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法(切记,不调用构造器)
子类去调用父类的属性和方法,不调用构造器
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo(){
} }
class Student {
public String name;
public int age;
public Date birthDate;
public String school;
public String getInfo() {
// ...
} }
这里的String和int都有重叠,所以我们用extends继承
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {
// ...
} }
class Student extends Person {
public String school; }
这里由于继承了Person,所以name和age不必再写了
java和python其中之一区别:java是单继承,通过多接口实现其他方法;而python是多继承
接口是多继承
Object类
* 1. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
* 2. 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
* 3. 意味着,所有的java类具有java.lang.Object类声明的功能。
public class ExtendsTest {
public static void main(String[] args) {
Person p1 = new Person();
// p1.age = 1;
p1.eat();
System.out.println("*****************");
Student s1 = new Student();
s1.eat();
// s1.sleep();
s1.name = "Tom";
s1.setAge(10);
System.out.println(s1.getAge());
s1.breath();
Creature c = new Creature();
System.out.println(c.toString());
}
}
练习
// Main.java
public class Main {
public static void main(String[] args) {
Cylinder p1 = new Cylinder();
// System.out.println(p1.findVolume());
p1.setLength(1.0);
p1.setRadius(1.0);
System.out.println(p1.findVolume());
}
}
// Circle.java
public class Circle {
private double radius;
public Circle(){
this.radius = 1;
}
//+setRadius(double radius) : void
//+getRadius(): double
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return this.radius;
}
//findArea():double
public double findArea(){
return Math.PI * getRadius() * getRadius();
}
}
// Cylinder.java
public class Cylinder extends Circle{
//-length:double
private double length;
public Cylinder(){
this.length = 1;
}
public void setLength(double length){
this.length = length;
}
public double getLength(){
return this.length;
}
public double findVolume(){
return Math.PI * getRadius() * getRadius() * getLength();
}
}
继承性和构造器关系
父类中最好有一个空参构造器
// Main.java
public class Main {
public static void main(String[] args) {
Student p1 = new Student();
}
}
// Student.java
public class Student extends Person{
String name;
Student(){
System.out.println("子类构造器");
}
}
// Person.java
public class Person {
String zhiye;
int age;
public Person(String zhiye, int age){
this.zhiye = zhiye;
this.age = age;
System.out.println("父类有参构造器");
}
public Person() {
System.out.println("父类无参构造器");
}
}
那么这里输出就是:"父类无参构造器" "子类构造器"
我们这里在Student的构造器中声明了无参构造器的话,必须在父类中也声明一个无参构造器
方法的重写
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为
static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
重写的规定:
① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
>特殊情况:子类不能重写父类中声明为private权限的方法
③ 返回值类型:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
静态方法无法被覆盖
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
} }
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
} }
super关键字
super可用于访问父类中定义的属性
super可用于调用父类中定义的方法
super可用于在子类构造器中调用父类的构造器
注意:
尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
super的追溯不仅限于直接父类 super和this的用法相像,this代表本类对象的引用,super代表父类的内存
空间的标识
* 3.super的使用:调用属性和方法
*
* 3.1 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用
* 父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
* 3.2 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的
* 使用"super.属性"的方式,表明调用的是父类中声明的属性。
* 3.3 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
* 使用"super.方法"的方式,表明调用的是父类中被重写的方法。
*
* 4.super调用构造器
* 4.1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
* 4.2 "super(形参列表)"的使用,必须声明在子类构造器的首行!
* 4.3 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
* 4.4 在构造器的首行,没有显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
* 4.5 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
class Person {
protected String name = "张三";
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age; } }
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool() {
return school; }
public String getInfo() {
return super.getInfo() + "\nschool: " + school;
}}
public class StudentTest {
public static void main(String[] args) {
Student st = new Student();
System.out.println(st.getInfo());
}}
练习
子类对象的实例化过程
* 1. 从结果上来看:(继承性)
* 子类继承父类以后,就获取了父类中声明的属性或方法。
* 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
*
* 2. 从过程上来看:
* 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...
* 直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有
* 父类中的结构,子类对象才可以考虑进行调用。
*
*
* 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}}
多态性
对象的多态性:父类的引用指向子类的对象 多态性只体现在方法
Java引用变量有两个类型:编译时类型和运行时类型
package com.atguigu.java4;
/*
* 面向对象特征之三:多态性
*
* 1.理解多态性:可以理解为一个事物的多种形态。
* 2.何为多态性:
* 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
Person p1 = new Man();
*
* 3. 多态的使用:虚拟方法调用
* 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
* 总结:编译,看左边;运行,看右边。
*
* 4.多态性的使用前提: ① 类的继承关系 ② 方法的重写
*
* 5.对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
*
* 6.多态解决了重载方法的设计,如下,如果我们没有多态性public void func(Animal animal)就会写很多遍,有了多态性我们只要使用test.func(new Dog());即可
多态是运行时行为
*/
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
//*************************************************
System.out.println("*******************");
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
// Person p3 = new Woman();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
p2.eat();
p2.walk();
// p2.earnMoney();
System.out.println(p2.id);//1001
}
}
package com.atguigu.java4;
import java.sql.Connection;
// 多态性不适用于属性!!!
//多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal){//Animal animal = new Dog();
animal.eat();
animal.shout();
if(animal instanceof Dog){
Dog d = (Dog)animal;
d.watchDoor();
}
}
// public void func(Dog dog){
// dog.eat();
// dog.shout();
// }
// public void func(Cat cat){
// cat.eat();
// cat.shout();
// }
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
//举例二:
class Order{
public void method(Object obj){
}
}
//举例三:
class Driver{
public void doData(Connection conn){//conn = new MySQlConnection(); / conn = new OracleConnection();
//规范的步骤去操作数据
// conn.method1();
// conn.method2();
// conn.method3();
}
}
虚拟方法调用
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类 的getInfo()方法 ——动态绑定
多态是运行时行为
编译时我们认为是看父类,实际运行时看的是子类的重写
向下转型的使用-instanceof
多态就是向上转型
// 编译时看左边,运行时看右边
我们在使用多态时, Person p1 = new Man();
编译时看左边,运行时看右边,如果我们的Person父类中有一个name属性,我们可以通过p1.name调用到
但是如果我们的Man子类中有一个isSmoking属性,我们则无法通过p1.isSomking调用到,因为我们在编译时是看左边,相当于Person中没有检测到isSomking,所以说多态的作用前提有两个:一个是有继承(子父类),另一个是重写,我们重写父类的方法,这样在编译时对方检测到父类Person中有该方法,而运行时也能执行到Man子类的方法;多态的作用就是为了解决虚拟方法调用的问题
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致
为了能调用子类特有的属性和方法
Person p1 = new Man();
Man m1 = (Man)p2;
这里我们可以把Person强转为Man类型,但是反过来我们就必须强制类型转换,叫做 向下转型:使用强制类型转换符
向下转型目的:为了能够既使用多态,又可以去直接调用子类属性,方法
使用强转时,可能出现ClassCastException的异常,instanceof关键字的使用
instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型
public classs Main{
public static void main(String[] args){
Person p1 = new man();
// 这里是new了一个对象,这里man是Person的子类
// 我们通过p1去调用方法,只能调用到父类的方法,这种向上转型就是多态性,如果想调用子类的功能那就是向下转型,用到instanceof
p1.getName();
}
}
package lightning;
public class ManKind {
public static void main(String[] args) {
KidsTest p1 = new Kids();
System.out.println(p1.name);
Kids p2 = (Kids)p1;
System.out.println(p2.love);
}
}
package com.atguigu.java;
import java.util.Date;
/*
* 面向对象特征之三:多态性
*
* 1.理解多态性:可以理解为一个事物的多种形态。
* 2.何为多态性:
* 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
*
* 3. 多态的使用:虚拟方法调用
* 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
* 总结:编译,看左边;运行,看右边。
*
* 4.多态性的使用前提: ① 类的继承关系 ② 方法的重写
*
* 5.对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
*
* *************************************************************
*
*
*/
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
//*************************************************
System.out.println("*******************");
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
// Person p3 = new Woman();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
p2.eat();
p2.walk();
// p2.earnMoney();
System.out.println(p2.id);//1001
System.out.println("****************************");
//不能调用子类所特有的方法、属性:编译时,p2是Person类型。
p2.name = "Tom";
// p2.earnMoney();
// p2.isSmoking = true;
//有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致
//编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
//如何才能调用子类特有的属性和方法?
//向下转型:使用强制类型转换符。
Man m1 = (Man)p2;
m1.earnMoney();
m1.isSmoking = true;
//使用强转时,可能出现ClassCastException的异常。
// Woman w1 = (Woman)p2;
// w1.goShopping();
/*
* instanceof关键字的使用
*
* a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
*
*
* 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先
* 进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
*
* 如果 a instanceof A返回true,则 a instanceof B也返回true.
* 其中,类B是类A的父类。
*/
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("******Woman******");
}
if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.earnMoney();
System.out.println("******Man******");
}
if(p2 instanceof Person){
System.out.println("******Person******");
}
if(p2 instanceof Object){
System.out.println("******Object******");
}
// if(p2 instanceof String){
//
// }
//练习:
//问题一:编译时通过,运行时不通过
//举例一:
// Person p3 = new Woman();
// Man m3 = (Man)p3;
//举例二:
// Person p4 = new Person();
// Man m4 = (Man)p4;
//问题二:编译通过,运行时也通过
// Object obj = new Woman();
// Person p = (Person)obj;
//问题三:编译不通过
// Man m5 = new Woman();
// String str = new Date();
// Object o = new Date();
// String str1 = (String)o;
}
}
//class Order{
//
//}
练习
20:这里直接就是s.count,调用子类的count属性值是20
20:这里s.display调用子类的方法是20
true:这里我们Base b = s,是把s的地址给了b,所以引用数据类型比较地址,所以b == s,返回true
10:这里虽然用了多态,但是多态是只体现在方法,不体现在属性,所以b.count的值是父类的10
20:这里多态后,s.display运行时调用的是子类的display方法,所以是20
public class InstanceTest {
public static void main(String[] args) {
InstanceTest p1 = new InstanceTest();
p1.method(new Student());
}
public void method(Person e) {
if (e instanceof Graduate) {
System.out.println("a graduated student");
System.out.println("a student");
System.out.println("a person");
} else if (e instanceof Student) {
System.out.println("a student");
System.out.println("a person");
} else {
System.out.println("a person");
}
String info = e.getInfo();
System.out.println(info);
}
}
class Person {
protected String name="person";
protected int age=50;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
class Student extends Person {
protected String school="pku";
public String getInfo() {
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
}
class Graduate extends Student{
public String major="IT";
public String getInfo(){
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school+"\nmajor:"+major;
}
}
public class GeometricTest {
public static void main(String[] args) {
GeometricTest w1 = new GeometricTest();
Circle c1 = new Circle(2.3,"white",1.0);
w1.displayGeometricObject(c1);
Circle c2 = new Circle(3.3,"white",1.0);
w1.displayGeometricObject(c2);
boolean c3 = w1.equalsArea(c1,c2);
System.out.println("c1和c2的面积是否相等:" + c3);
MyRectangle m1 = new MyRectangle(6.0,2.0,"red",2.0);
MyRectangle m2 = new MyRectangle(4.0,3.0,"red",2.0);
w1.displayGeometricObject(m1);
boolean m3 = w1.equalsArea(m1,m2);
System.out.println("m1和m2的面积是否相等:" + m3);
}
public boolean equalsArea(GeometricObject p1,GeometricObject p2){
return p1.findArea() == p2.findArea();
}
public void displayGeometricObject(GeometricObject p){
System.out.println("面积为" + p.findArea());
}
}
class GeometricObject{
protected String color;
protected double weight;
public GeometricObject(String color, double weight){
super();
this.color = color;
this.weight = weight;
}
public void setColor(String color){
this.color = color;
}
public String getColor(){
return this.color;
}
public void setWeight(double weight){
this.weight = weight;
}
public double getWeight(){
return this.weight;
}
public double findArea(){
return 0.0;
}
}
class Circle extends GeometricObject{
private double radius;
public Circle(double radius,String color,double weight ){
super(color,weight);
this.radius = radius;
}
public double getRadius(){
return this.radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public double findArea(){
return 3.14 * radius * radius;
}
}
class MyRectangle extends GeometricObject{
private double width;
private double height;
public MyRectangle(double width,double height,String color,double weight){
super(color, weight);
this.width = width;
this.height = height;
}
public double getWidth(){
return this.width;
}
public void setWidth(double width){
this.width = width;
}
public double getHeight(){
return this.height;
}
public void setHeight(double height){
this.height = height;
}
public double findArea(){
return height * width;
}
}
练习3-重写方法
package com.atguigu.exer;
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base1 base = new Sub1();
base.add(1, 2, 3);
Sub1 s = (Sub1)base;
s.add(1,2,3);
}
}
class Base1 {
public void add(int a, int... arr) {
System.out.println("base1");
}
}
class Sub1 extends Base1 {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
Object的使用
Object类是所有Java类的根父类
如果在类的声明中未使用extends关键字指明其父类,则默认父类 为java.lang.Object类
public class Person {
...
}
等价于
public class Person extends Object {
...
}
也就是说,我们子类的父类,如果没有明确指定他的父类,那么Object就是他的父类
方法:equals() / toString() / getClass() /hashCode() / clone() / finalize() / wait() 、 notify()、notifyAll()
== 和 equals()方法 区别
==:运算符
可以使用在基本数据类型变量和引用数据类型变量中
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
equals()方法的使用:
是一个方法,而非运算符,只能适用于引用数据类型
public boolean equals(Object obj) {
return (this == obj);
}
Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写.重写的原则:比较两个对象的实体内容是否相同.
public class EqualsTest {
public static void main(String[] args) {
//基本数据类型
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j);//true
System.out.println(i == d);//true
boolean b = true;
// System.out.println(i == b);
char c = 10;
System.out.println(i == c);//true
char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2);//true
//引用类型:
Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
System.out.println(cust1 == cust2);//false
String str1 = new String("atguigu");
String str2 = new String("atguigu");
System.out.println(str1 == str2);//false
System.out.println("****************************");
System.out.println(cust1.equals(cust2));//false
System.out.println(str1.equals(str2));//true
Date date1 = new Date(32432525324L);
Date date2 = new Date(32432525324L);
System.out.println(date1.equals(date2));//true
}
}
//自动生成的equals()
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
static关键字
static:某些特定的数据在内存空间里只有一份
static可以用来修饰:属性、方法、代码块、内部类
使用static修饰属性:静态变量(或类变量)
属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的
static修饰属性的其他说明:
* ① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
Chinese.nation = "中国";
* ② 静态变量的加载要早于对象的创建,所以当我们创建类时就会创建静态变量
Chinese c1 = new Chinese();
* ③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
静态变量 非静态变量
类 yes no
对象 yes yes
静态方法中,只能调用静态的方法或属性,非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN";
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA";
System.out.println(c1.nation);
-------------------------------------------
String name;
int age;
static String nation;
这里我们的nation是被static修饰的,
静态变量和实例变量内存解析
static修饰方法
因为static是随着类的加载而加载,所以我们创建类时,可以直接去调用方法;和静态变量类似的,也可以创建对象去调用类的方法
静态变量 非静态变量
类 yes no
对象 yes yes
static注意点:
在静态的方法内,不能使用this关键字、super关键字
关于静态属性和静态方法的使用,从生命周期的角度去理解
public static void show(){
System.out.println("我是一个中国人!");
//不能调用非静态的结构
eat(); // 这里不能加this关键字、super关键字
// name = "Tom";
//可以调用静态的结构
System.out.println(Chinese.nation);
walk();
}
如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的
类中的常量也常常声明为static
如何确定一个方法是否要声明为static的?
操作静态属性的方法,通常设置为static的,比如我们的属性 name=20;那么我们的getName和setName方法都应该设置为静态
static示例
public class Main {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
System.out.println(c1.getId());
System.out.println(c2.getId());
System.out.println(Circle.total);
}
}
class Circle{
private double radius;
private int id;
public Circle(){
id = init++;
total++;
}
public Circle(double radius){
this();
this.radius = radius;
}
static int total = 0;
private static int init = 1001;
public double findArea(){
return 3.14 * radius * radius;
}
public int getId(){
return id;
}
public static int getTotal(){
return total;
}
}
main()方法
main()方法作为程序的入口
main()方法也是一个普通的静态方法
main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
public class MainTest {
public static void main(String[] args) {//入口
Main.main(new String[100]);
MainTest test = new MainTest();
test.show();
}
public void show(){
}
}
class Main{
public static void main(String[] args) {
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
代码块
代码块的作用:用来初始化类、对象
//非static的代码块
{
System.out.println("hello, block - 2");
}
代码块如果有修饰的话,只能使用static
//static的代码块
static{
System.out.println("hello,static block-2");
}
当我们调用类的静态属性时,类的静态属性和静态方法都会加载到内存
String desc = Person.desc;
分类:静态代码块 vs 非静态代码块
静态代码块:
内部可以有输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块:
内部可以有输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
代码块会先构造器执行
练习1
package com.atguigu.java3;
//总结:由父及子,静态先行
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
//System.out.println();
//new Leaf();
}
}
输出
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
原理
首先我们使用匿名对象,new Leaf();时,首先是执行的 Leaf(); 是调用类
在类 Leaf() 的构造器里,我们super("尚硅谷"); 去调用父类,父类的有参构造器里面调用this();
也就是调用自己的无参构造器,他再去调用super();,去找他自己的父类;他的父类再调用super();,他的父类是
Object类,所以首先会加载和Object类相关静态信息
//因为此时我们还没有创建对象,只是在调用类,所以我们会先执行静态属性
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
//又因为代码块比构造器先执行,所以
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
//如果我们在下面增加两行代码
System.out.println();
new Leaf();
//那么由于静态代码块只调用一次,所以最后的结果会增加如下
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
练习2
package com.atguigu.java3;
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
super();
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
super();
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
// System.out.println("************************");
// new Son();
// System.out.println("************************");
// new Father();
}
}
输出
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
原理
这里 public static void main(String[] args) 是主函数入口,main主函数是static静态方法,如果调用静态方法,那么说明已经加载类了,所以我们已经加载了son这个类,而在son类里的构造器这里,因为我们的son类是father类的子类,所以构造器这里,默认其实是有一个super();去调用父类father的构造器,father的构造器也有一个super();所以他会去找Object,依次调用Object->Father->Son的静态代码块,所以执行
11111111111
44444444444
然后执行
77777777777
************************
执行后我们 new Son(); 首先是调用类,其次是创建对象,当我们调用类时,和练习1一样
22222222222
33333333333
55555555555
66666666666
final关键字
final可以用来修饰的结构:类、方法、变量
final 用来修饰一个类:此类不能被其他类所继承,比如:String类、System类、StringBuffer类
final 用来修饰方法:表明此方法不可以被重写,比如:Object类中getClass();
final 用来修饰变量:此时的"变量"就称为是一个常量
final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
//显式初始化
final int WIDTH = 0;
//代码块中初始化
final int LEFT;
{
LEFT = 1;
}
//构造器中初始化
final int RIGHT;
public FinalTest(){
RIGHT = 2;
}
public FinalTest(int n){
RIGHT = n;
}
// 不能通过调用方法去修改final修饰的属性
原因:当我们调用类时,首先调用构造器,这时把属性加载,此时堆中必须有值,如果我们通过对象去调用方法本质上是对值再修改,但是被final修饰的常量,无法被修改
final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
public void show(final int num){
// num = 20;//编译不通过
System.out.println(num);
}
test.show(10);
static final 用来修饰属性:全局常量
练习
抽象类与抽象方法
抽象类:将一个父类设计得非常抽象,以至于它没有具体的实例
abstract可以用来修饰的结构:类、方法
abstract修饰类:抽象类
1.此类不能实例化
2.抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
3.开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
// 抽象类
abstract class Creature{
public abstract void breath();
}
// 抽象方法
public abstract void eat();
即,我们子类的父类(或者再父类)中包含abstract抽象方法,那么要么我们重写该方法,要么我们的子类也是抽象类