键盘输入
接收用户输入的数据,使用键盘输入语句来获取。Input.java,需要一个扫描器(对象)Scanner
import java.util.Scanner;
public class Input {
public static void main(String[] args) {
//Scanner类表示简单的文本扫描器 在java.util包
//1.导入Scanner类所在的包
//2.创建Scanner对象
Scanner myScanner = new Scanner(System.in);
// 接收用户输入 使用相关方法
System.out.println("请输入名字");
String name = myScanner.next();
System.out.println(name);
System.out.println("请输入你的年龄");
int age = myScanner.nextInt();
System.out.println(age);
System.out.println("请输入你的薪水");
double sal = myScanner.nextDouble();
System.out.println(sal);
}
}
打印九九乘法表
public class ForExercise {
public static void main(String[] args) {
for (int i=1; i<=9;i++){ //行
for (int j=1;j<=i;j++){ //列
System.out.print( j + "*" + i +"="+ i*j +'\t');
}
System.out.println();
}
}
}
/*
输出:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
*/
类与对象
类就是数据类型
对象就是一个具体的实例
public class Object01 {
public static void main(String[] args){
Cat cat = new Cat();
cat.name = "xiaohua";
cat.age = 2;
cat.color="red";
System.out.println("name is : " + cat.name + "age is : "+ cat.age
+ "color is : " + cat.color);
}
}
class Cat{
String name;
int age;
String color;
}
//输出
name is : xiaohuaage is : 2color is : red
创建对象
- 先声明再创建
Cat cat;
cat = new Cat();
- 直接创建
Cat cat = new Cat()
成员方法
成员方法的定义
public 返回数据类型 方法名 (形参列表……) {//方法体
语句;
return 返回值;
}
public class Object02 {
public static void main(String[] args) {
Person p1 =new Person();
p1.speak();
p1.cal01();
p1.cal02(10);
System.out.println(p1.getSum(2,7));
}
}
class Person{
String name;
int age;
public void speak(){
System.out.println("我是一个好人!");
}
// 添加成员方法 ,可以从1 + …… +100的结果
public void cal01(){
int res = 0;
for (int i =1;i<=1000;i++){
res +=i;
}
System.out.println(res);
}
//添加cal02 成员方法,该方法可以接收一个数n,计算1 + …… + n
//1. (int n) 形参列表 , 表示当前有一个形参
public void cal02(int n){
int res = 0;
for (int i = 1;i <= n; i++){
res +=i;
}
System.out.println(res);
}
//getSum成员方法,计算两个数的和
//public 表示方法是公开的
//getSum 方法名
//(int num1, int num2) 形参列表
public int getSum( int num1, int num2){
int res = num1 + num2;
return res;
}
}
public class Method01 {
public static void main(String[] args) {
int [][] map = {{0,0,1},{1,1,1},{1,1,3}};
// //遍历一个数组
// for(int i=0;i<map.length;i++){
// for(int j=0;j<map[i].length;j++){
// System.out.print(map[i][j] + " ");
// }
// System.out.println();
// }
//要求再次 遍历
Mytools tool = new Mytools();
tool.printArr(map);
tool.printArr(map);
tool.printArr(map);
}
}
class Mytools{
public void printArr(int[][] map){
System.out.println("======");
for(int i=0;i<map.length;i++){
for(int j=0;j<map[i].length;j++){
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
方法使用细节
- 一个方法最多有一个返回值 返回多个结果 数组
- 返回类型可以为任意类型,包含基本类型或引用类型(对象、数组)
- 如果方法要求有返回数据类型,则方法体中最后执行语句为 return 值;返回数据类型和return的值类型一致或兼容
- 如果方法是void,则方法中可以没有return语句,或只写return;方法名 驼峰命名法
- 方法体内不能定义方法
同一个类中的方法: 直接调用
public class MethodDetail02 {
public static void main(String[] args) {
A a = new A();
a.sayOk();
}
}
class A{
//同一个类中的方法调用: 直接调用
public void print(int n){
System.out.println("print()方法被调用 n=" + n);
}
public void sayOk(){ //sayOk调用 print 直接调用
print(10);
}
}
跨类中的方法A调用B类方法: 需要通过对象名调用。
跨类的方法调用和方法的访问修饰符相关。
public class MethodDetail02 {
public static void main(String[] args) {
A a = new A();
a.sayOk();
a.m1();
}
}
class A{
//同一个类中的方法调用: 直接调用
public void print(int n){
System.out.println("print()方法被调用 n=" + n);
}
public void sayOk(){ //sayOk调用 print 直接调用
print(10);
}
//跨类中的方法A调用B类方法: 需要通过对象名调用。
public void m1(){
System.out.println("m1()方法被调用");
B b = new B();
b.hi();
}
}
class B{
public void hi(){
System.out.println("B类中的 hi()被执行");
}
}
输出:
print()方法被调用 n=10
m1()方法被调用
B类中的 hi()被执行
小练习1
编写类BB ,有一个方法: 判断一个数是偶数odd还是奇数,返回boolean
package Object;
import sun.font.TrueTypeFont;
public class MethodWxercise01 {
public static void main(String[] args) {
BB a = new BB();
if (a.is_Odd(6))
System.out.println("是偶数");
else{
System.out.println("是奇数");
}
}
}
class BB{
//1.方法的返回类型 boolean
//2. 方法的名字
public boolean is_Odd(int n){
// if (n%2 == 0 ){
// return true;
// }else {
// return false;
// }
return n % 2 == 0 ? true:false; //三元运算 //条件表达式
}
}
小练习2
//根据行、列字符打印,对应行数和列数的字符
package Object;
public class MethodExercise02 {
public static void main(String[] args) {
CC c = new CC();
c.print(4,4,'#');
}
}
class CC{
public void print(int row,int col,char c){
for (int i = 0 ; i < row; i++){
for (int j = 0; j < col ;j++){
System.out.print(c);
}
System.out.println();
}
}
}
传参机制
基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参。
引用数据类型,传递的是值(地址)。(对象、数组)
方法递归调用
递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂问题,同时可以让代码变 得简洁
public class Recursion01 {
//编写一个 main 方法
public static void main(String[] args) {
T t1 = new T();
t1.test(4);//输出什么? n=2 n=3 n=4
int res = t1.factorial(5);
System.out.println("5 的阶乘 res =" + res);
}
}
class T {
//分析
public void test(int n) {
if (n > 2) {
test(n - 1);
}
System.out.println("n=" + n);
}
//factorial 阶乘
public int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n;
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0M56PMl-1668775946938)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221008214038879.png)]
方法的重载
java 中允许同一个类中,多个同名方法的存在,但要求 形参列表不一致! 比如:System.out.println(); out 是 PrintStream 类型
package OverLoad;
public class Overload01 {
public static void main(String[] args) {
MyCalculater calculater = new MyCalculater();
System.out.println(calculater.calculate(1,2));
System.out.println(calculater.calculate(2,2.5));
System.out.println(calculater.calculate(5.2,5));
System.out.println(calculater.calculate(1,2,3));
}
}
class MyCalculater{
//下面的四个方法构成了方法的重载
//两个整数的和
public int calculate(int n1, int n2){
return n1 + n2;
}
// 一个整数,一个double
public double calculate(int n1, double n2){
return n1 + n2;
}
//一个double 一个整数
public double calculate(double n1, int n2){
System.out.println("被调用");
return n1 + n2;
}
//三个int 的和
public int calculate(int n1,int n2,int n3){
return n1 + n2 + n3;
}
}
/*
3
4.5
被调用
10.2
6*/
注意事项
1)方法名: 必须相同
2)参数列表: 必须不同(参数类型或个数或顺序,至少有一样不同,参数名无要求)
3)返回类型: 无要求
public int calculate(int n1, int n2){
return n1 + n2;
}
public double calculate(int n1, int n2){
return n1 + n2;
}
//重复定义报错
可变参数
基本概念: java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
基本语法: 访问修饰符 返回类型 方法名(数据类型… 形参名)
public class VarParameter {
public static void main(String[] args) {
HspMethod m = new HspMethod();
System.out.println(m.sum(1,2,3,4,5));
}
}
class HspMethod{
// 可以计算2个数的和,3个数的和,4,5.。
//可以使用方法重载
// public int sum(int n1, int n2){ //2个数的和
// return n1 + n2;
// }
// public int sum(int n1, int n2, int n3){ //3个数的和
// return n1 + n2 + n3;
// }
// public int sum(int n1, int n2, int n3, int n4){ //4个数的和
// return n1 + n2 + n3 + n4;
// }
//……
//上面的三个方法名称相同,功能相同,参数不同———》使用可变参数
//1.int... 表示接收的是可变参数,类型是int,即可以接收多个int(0-多)
//2.使用可变参数时,可以当作数组使用
public int sum(int... sums){
System.out.println("接收的参数个数=" + sums.length);
int res =0;
for (int i=0; i< sums.length; i++){
res += sums[i];
}
return res;
}
}
注意细节
可变参数的实参 可以为0或任意多个
可变参数的实参可以为数组
可变参数的本质就是数组
可变参数可以和普通型的参数一起放在形参列表,但必须保证可变参数放在最后
一个形参列表中只能出现一个可变参数
public class VarParameter01 {
public static void main(String[] args) {
int[] arr = {1,2,3};
T t = new T();
t.sum(arr);
}
}
class T {
public int sum(int... sums){
System.out.println(sums.length);
return 0;
}
//细节: 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
public void f2(String str, double... sums){
}
}
可变参数的使用
有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课(总分)。封装成一个可变参数的方法
public class VarParameterExercise {
public static void main(String[] args) {
HspMethod2 h = new HspMethod2();
String score = h.showScore("kang",98.5,100.0,100.0,100.0,100.0);
System.out.println(score);
}
}
class HspMethod2 {
public String showScore(String name,double... scores){
double totalScore = 0;
for (int i =0 ;i < scores.length; i++){
totalScore += scores[i];
}
return name + "成绩总分为=" + totalScore;
}
}
作用域
全局变量(成员变量): 也就是属性,作用域为整个类体 Cat类: cry eat 等方法使用属性
属性在定义时,可以直接赋值。全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。
public class VarScope {
public static void main(String[] args) {
}
}
class Cat{
//全局变量: 也就是属性,作用域为整个类体 Cat类: cry eat 等方法使用属性
//属性在定义时,可以直接赋值
int age = 10;
public void cry(){
//1.局部变量一般是指在成员方法中定义的变量
//2.n 和name 就是局部变量
//3. n 和name 的作用域在cry方法中
int n = 10;
String name = "jack";
System.out.println("cry中使用属性 age=" + age );
}
public void eat(){
System.out.println("在eat中使用属性 age=" + age);
// System.out.println("在eat中使用属性 name=" + name); //错误
}
}
使用细节 属性和局部变量可以重名,访问时遵循就近原则。
public class VarScopeDetail {
public static void main(String[] args) {
Person p1 = new Person();
p1.say();
}
}
class Person{
String name = "jack";
public void say(){
//细节 属性和局部变量可以重名,访问时遵顼就近原则
String name = "zhaoguoyu";
System.out.println("say() name=" + name);
}
}
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短
,伴随着他的代码块执行而执行,伴随着代码块的销毁而销毁,既在一次方法调用过程中。
全局变量/属性: 可以被本类使用,或其他类使用(通过对象调用)
局部变量只能在本类中对应的方法中使用
全局变量/属性可以加修饰符
局部变量不可以加修饰符
public class VarScopeDetail {
public static void main(String[] args) {
Person p1 = new Person();
// p1.say();
T t1 = new T();
t1.giao();
t1.test(p1);
}
}
class T{
public void giao(){
Person p1 = new Person();
System.out.println(p1.name);
}
public void test(Person p){
System.out.println(p.name);
}
}
class Person{
String name = "jack";
public void say(){
//细节 属性和局部变量可以重名,访问时遵顼就近原则
String name = "zhaoguoyu";
System.out.println("say() name=" + name);
}
}
构造方法/构造器
. 基本语法
修饰符 方法名 (形参列表){
方法体;
}
1)构造器的修饰符可以默认,也可以是public protected private
2)构造器没有返回值
3)方法名和类名必须一样
4)参数列表和成员方法一样的规则
5)构造器的调用由系统完成
构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
1)方法名和类名相同
2)没有返回值
3)在创建对象时,系统会自动的调用该类的构造器完成对对象的初始化。
如果没有定义构造方法,系统会自动给类生成一个默认无参构造方法也叫(默认构造方法)
一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式定义一下,Person(){}
public class Constructor01 {
public static void main(String[] args) {
Person1 p1 = new Person1("smith", 80);
System.out.println(p1.name + '\t' + p1.age);
}
}
class Person1 {
String name;
int age;
// 构造器
// 构造器没有返回值
// 构造器的名称和类Person一样
// (String pName, int pAge)是构造器形参列表,规则和成员方法一样
public Person1(String pName, int pAge){
System.out.println("构造器被调用");
name = pName;
age = pAge;
}
}
定义一个Person2类添加两个构造器
第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
第二个带pName和pAge两个参数的构造器
使得每次创建Person对象的同时初始化对象的age属性值和name属性值。
分别使用不同的构造器,创建对象
public class ConstructorExercise {
public static void main(String[] args) {
Person2 p2 = new Person2();//无参构造器
System.out.println(p2.age + "name" + p2.name); // 18namenull
Person2 p3 = new Person2("shabi",22);
System.out.println(p3.name + '\t' + p3.age);
}
}
class Person2{
String name;
int age;
//第一个无参构造器: 利用构造器设置所有人的age属性初始值都为18
public Person2(){
age = 18;
}
//第二个带pName和pAge两个参数的构造器
public Person2(String pName, int pAge){
name = pName;
age = pAge;
}
}
this关键字
java虚拟机会给每个对象分配this,代表当前对象。
this关键字可以用来访问本类的属性、方法、构造器
this用于区分当前类的属性和局部变量
访问成员方法的语法: this.方法名(参数列表)
访问构造器语法: this(参数列表);只能在构造器中使用 构造器中调用另一个构造器
this不能在类定义的外部使用,只能在类定义的方法中使用
public class ThisDetail {
public static void main(String[] args) {
Th t = new Th();
t.f2();
}
}
class Th{
/*
细节访问构造器语法 this(参数列表)
*/
public Th(){
this("jack",18); //必须放在构造器的第一条语句
System.out.println("Th() 构造器");
}
public Th(String name, int age){
System.out.println("有参构造器");
System.out.println(name + age);
}
// 细节: 访问成员方法的语法: this.方法名(参数列表)
public void f1(){
System.out.println("f1() 方法。。");
}
public void f2(){
System.out.println("f2() 方法");
// 调用本类的f1方法
this.f1();
}
}
public class TestClass {
public static void main(String[] args) {
Student student = new Student();//对象创建
System.out.println(student.getName());
System.out.println(student.age);// 默认值
Student student1 = new Student(22); // 初始化对象,调用自定义构造方法
System.out.println(student1.getName());
System.out.println(student1.getAge());
Student student2 = new Student("何明康",22);
System.out.println(student2.getName());
System.out.println(student2.getAge());
}
}
class Student{
//成员属性
String name;
int age;
public Student(){
name ="张三";
}
public Student(int age){
this(); //代表当前类的另一个构造方法,只能用在当前类的重载构造方法。
this.age=age;
}
public Student(String name,int age){
this(age);
this.name=name;
}
//成员方法 set get
public void setName(String name){
this.name=name;
}
public void setName(int age){
this.age=age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
冒泡排序
public class MyTools {
public static void main(String[] args) {
MaoPao m = new MaoPao();
int a[] = {12,3,6,10,9,8};
m.Pai(a);
//输出排序后的 数组
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + "\t");
}
}
}
class MaoPao{
int temp = 0;
public void Pai(int arr[]){
for (int i = 0; i < arr.length -1 ; i++) { //循环的次数
for (int j = 0; j < arr.length - 1 - i ; j++) {
if (arr[j] > arr[j+1]){ //交换
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
输出:
3 6 8 9 10 12
Idea(集成开发环境) -IDEA的使用
idea常用快捷键
1.删除当前行默认ctrl + y
2.复制当前行ctrl + d
3.补全代码alt + /
4.添加取消注释 ctrl + /
5.导入该行需要的类 alt + enter
6.快速格式化代码 ctrl + alt+ L
7.生成构造方法 alt + insert
8.查看一个类的层级关系 ctrl + H 可以右击可以查看图
9.将光标放到一个方法上,输入ctrl + B,可以选择定位到哪个类的方法
10.自动的分配变量名,通过在后面.var
11.等等自定义
idea自定义模板
模板自定义模板
file-> settings -> editor -> Live templates -> 查看现有的模板
包
包的三大作用
1.区分相同名字的类
2.当类很多时,可以很好的管理类 【Java API 文档】
3.控制访问范围
包基本语法
package com.hmklove
说明:
- package : 关键字,表示打包
- com.hmklove: 表示包名
Java包原理
包的本质分析:(原理)
包的本质实际上就是创建不同的文件夹来保存类文件
包的快速入门
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qw6k2KKU-1668775946939)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221015205331106.png)]
package com.user;
import com.xiaoming.Dog;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog);
com.xiaoqiang.Dog dog1 = new com.xiaoqiang.Dog();
System.out.println(dog1);
}
}
/*
com.xiaoming.Dog@1b6d3586
com.xiaoqiang.Dog@4554617c
*/
包命名
命名规则:
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字、保留字
命名规范:
一般是小写字母+小圆点一般是
com.公司名.项目名.业务模块名
举例: com.sina.crm.user //用户模块
常用的包
一个包下,包含很多的类,java中常用的包有:
- java.lang.* //lang包是基本包,默认引入,不需要再引入
- java.util.* //util包,系统提供的工具包,工具类,使用Scanner
- java.net.* //网络包,网络开发
- java.awt.* //是做java的界面开发,GUI
如何导入包
语法: import 包
- 引入一个包的主要目的是使用该包下的类
- 比如import.java.util.Scanner;就只是引入一个类Scanner
- import java.util.*;表示将java.util包所有都引入
package com.hmk.pkg;
// 建议需要使用哪个类就导入哪个类
import java.util.Arrays;
import java.util.Scanner;
public class Import01 {
public static void main(String[] args) {
// 使用系统提供的 Arrays 完成数组排序
int arr[] = {-1, 20, 2, 13, 3};
// 系统提供了相应的类,完成排序
Arrays.sort(arr); //静态方法 不需要对象来调用 类名.方法名()
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
注意事项和使用细节
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package。
- import 指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序。
访问修饰符规则
java提供了四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用public修饰,对外开放
- 受保护级别:用protected修饰,对子类和同一个包中的类公开
- 默认级别: 没有修饰符号,向同一个包的类公开
- 私有级别:用private修饰,只有类本身可以访问,不对外公开
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCzk39QV-1668775946940)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221015220014717.png)]
package com.hmk.modifier;
public class A {
// 四个属性,分别使用不同的访问修饰符来修饰
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public void m1(){
//该方法可以访问 四个属性
System.out.println("n1=" + n1 + "n2=" + n2 + "n3=" + n3 + "n4" + n4);
}
}
package com.hmk.modifier;
public class B {
public void say(){
A a = new A();
//在用一个包下可以访问 public protected 和默认, 不能访问 private
System.out.println("n1=" + a.n1 + "n2=" + a.n2 + " n3=" + a.n3 );
}
}
package com.hmk.modifier;
public class Test {
public static void main(String[] args) {
A a = new A();
a.m1();
B b = new B();
b.say();
}
}
// 只有 默认 和public 才能修饰类
不同包只能访问 public修饰的属性
package com.hmk.pkg;
import com.hmk.modifier.A;
public class Test {
public static void main(String[] args) {
// 在不同包下,可以访问public 修饰的属性或方法
// 但是不能访问 protected , 默认 ,provate 修饰的属性或方法
A a = new A();
System.out.println(a.n1);
}
}
封装
封装介绍
封装就是把抽象出的数据[属性]和对数据的操作[方法] 封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
好处: 1. 隐藏实现细节 2. 可以对数据进行验证,保证安全合理
封装实现的步骤:
-
将属性进行私有化private 【不能直接修改属性】
-
提供一个公共的set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){ // 加入验证的业务逻辑 属性名=参数名; }
-
提供一个公共的方法(pubic)get方法,用于获取属性的值
public 数据类型 getXxx(){ // 权限判断,Xxx某个属性 return xx; }
package com.hmk.encap;
public class Encapsulation {
public static void main(String[] args) {
Person person = new Person();
person.setName("jack");
person.setAge(18);
person.setSalery(30000);
System.out.println(person.showInfo());
Person person1 = new Person("simth",19,40000);
System.out.println(person1.showInfo());
}
}
class Person{
public String name;
private int age;
private double salery;
public Person() {
}
public Person(String name, int age, double salery) {
// this.name = name;
// this.age = age;
// this.salery = salery;
setName(name);
setAge(age);
setSalery(salery);
}
// get set 快捷键 alt + insert
public String getName() {
return name;
}
public void setName(String name) {
// 可以在相关的方法 加入相关的业务逻辑
if (name.length() >=2 && name.length() <=6){
this.name=name;
}else {
System.out.println("name长度不够,默认名字Ikun");
this.name= "Ikun";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >=1 && age <=120){
this.age = age;
}else {
System.out.println("设置的年龄有误 ,给默认年龄 18");
this.age = 18;
}
}
public double getSalery() {
return salery;
}
public void setSalery(double salery) {
this.salery = salery;
}
// 写一个方法 返回信息
public String showInfo(){
return "姓名为name=" + name + " "+ "年龄age=" + age + " "+ "薪水salery=" + salery;
}
}
//姓名为name=jack 年龄age=18 薪水salery=30000.0
//姓名为name=simth 年龄age=19 薪水salery=40000.0
继承
解决代码复用
- 当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
继承的基本语法
class 子类 extends 父类{
}
1)子类就会自动拥有父类定义的属性和方法
2)父类又叫超类,基类
3)子类又叫派生类
- 子类继承了所有的属性和方法,但是私有属性和方法不能直接在子类直接访问,需要通过父类提供公共的方法去访问。
- 子类必须调用父类的构造器,完成父类的初始化(相当于子类构造器有一个隐藏的super)
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不通过。
- 如果希望只当去调用父类的某个构造器,则显式的调用一下:super(参数列表)
- super在使用时,需要放在构造器的第一行
- super()和this() 都只能放在构造器的第一行,所以这两个方法不能共存在一个构造器。
- java所有类都是Object类的子类,Object是所有类的基类
- 父类构造器的调用不限于直接父类!将一直往上追溯直到Object类(顶级父类)
- 子类最多只能继承一个父类(直接继承),java中是单继承机制
- 不能滥用继承,子类和父类之间必须满足is-a的逻辑关系
package com.extend.improve_;
public class ExtendExercise01 {
public static void main(String[] args) {
B b = new B();
}
}
class A {
A() {
System.out.println("a");
}
A(String name) {
System.out.println("a name");
}
}
class B extends A{
B(){
this("abc");
System.out.println("b");
}
B(String name){
// 默认super();
System.out.println("b name");
}
}
/* 输出:
a
b name
b
*/
package com.hmk;
import org.omg.CORBA.PUBLIC_MEMBER;
public class ExtendsExercise01 {
public static void main(String[] args) {
C c = new C();
}
}
class A{
public A(){
System.out.println("我是A类");
}
}
class B extends A{
// super();
public B(){
System.out.println("我是B类的无参构造");
}
public B(String name){
System.out.println(name + "我是B类的有参构造");
}
}
class C extends B{
public C(){
this("hello");
System.out.println("我是C类的无参构造");
}
public C(String name){
super("hahaha");
System.out.println("我是C类的有参构造");
}
}
/*我是A类
hahaha我是B类的有参构造
我是C类的有参构造
我是C类的无参构造*/
编写Computer 类,包含内存、cpu,硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性brand
package com.extend.improve_.exercise;
public class Computer {
private String cpu;
private int memory;
private int disk;
public Computer(String cpu, int memory, int disk){
this.cpu=cpu;
this.memory=memory;
this.disk=disk;
}
// 返回信息
public String getDetails(){
return "cpu=" + cpu + " memory" + memory + " disk=" + disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
}
package com.extend.improve_.exercise;
// 编写PC类,继承Computer类,添加特有属性品牌brand
public class PC extends Computer{
private String brand;
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void printInfo(){
System.out.println(getDetails() + " brand=" + brand);
}
}
package com.extend.improve_.exercise;
public class ExtendsExercise {
public static void main(String[] args) {
PC pc = new PC("inter", 64, 500, "IBM");
pc.printInfo();
}
}
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器
- 访问父类的属性,但不能访问父类的private属性 super.属性名
- 访问父类的方法,但不能访问父类的private方法 super.方法名(参数列表)
- 访问父类的构造器 super(参数列表);只能放在构造器的第一句,只能出现一句。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WQ6IOCxV-1668775946941)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221017214611844.png)]
方法重写/覆盖(override)
方法重写就是子类有一个方法,和父类的某个方法的名称,返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法。
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类,比如父类返回类型是Object,子类方法返回类型是String。
- 子类方法不能缩小父类方法的访问权限
多态(polymorphic)
多态的基本介绍
方法或对象具有多态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
方法的多态
重写和重载就体现多态
对象的多态
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义时 = 号的左边,运行类型 看 = 号的右边
Animal animal = new Dog(); animal的编译类型是Animal 运行类型Dog
animal = new Cat(); animal的运行类型变成Cat,编译类型仍然是Animal
package com.hmk.poly.ObjectPoly;
//父类
public class Animal {
public void cry(){
System.out.println("Animal cry() 动物在叫");
}
}
package com.hmk.poly.ObjectPoly;
public class Dog extends Animal{
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫……");
}
}
package com.hmk.poly.ObjectPoly;
public class Cat extends Animal{
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫 ");
}
}
package com.hmk.poly.ObjectPoly;
public class PolyObject {
public static void main(String[] args) {
//体验对象多态特点
//animal 编译类型就是Animal ,运行类型Dog
Animal animal = new Dog();
//因为运行时,执行到该行时,animal运行类型是Dog
animal.cry();// Dog cry() 小狗汪汪叫……
animal = new Cat();
animal.cry(); // Cat cry() 小猫喵喵叫
}
}
多态的前提是: 两个对象(类) 存在继承关系
多态的向上转型
- 本质: 父类的引用指向了子类的对象
- 语法: 父类类型 应用名 = new 子类类型();
- 特点: 编译类型看左边,运行类型看右边。 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中的特有成员。
package com.hmk.poly.detail;
public class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
package com.hmk.poly.detail;
public class Cat extends Animal{
public void eat(){ //方法的重写
System.out.println("猫吃鱼");
}
public void catchMouse(){ //Cat特有的方法
System.out.println("猫抓老鼠");
}
}
package com.hmk.poly.detail;
public class Polydetail {
public static void main(String[] args) {
// 向上转型 父类的引用指向了子类的对象
Animal animal = new Cat();
// Animal animal1 = new Animal();
// animal1.eat();
Object object = new Cat(); //可以 Object也是 Cat的父类
// 可以调用父类中的所有成员属性(但遵守访问权限)
// 但是不能调用子类的特有的成员
// 在编译阶段,能调用哪些成员,由编译类型来决定的
// animal。catchMouse();错误
// 最终运行效果看子类(运行类型)的具体实践,即调用方法时,按照从子类(运行类型)开始查找方法
animal.eat();
animal.run();
animal.show();
animal.sleep();
}
}
多态的向下转型
- 语法: 子类类型 引用名 = (子类类型) 父类引用;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 可以调用子类类型中所有的成员
package com.hmk.poly.detail;
public class Polydetail {
public static void main(String[] args) {
// 向上转型 父类的引用指向了子类的对象
Animal animal = new Cat();
// Animal animal1 = new Animal();
// animal1.eat();
Object object = new Cat(); //可以 Object也是 Cat的父类
// 向上转型调用方法的规则
// 可以调用父类中的所有成员属性(但遵守访问权限)
// 但是不能调用子类的特有的成员
// 在编译阶段,能调用哪些成员,由编译类型来决定的
// animal。catchMouse();错误
// 最终运行效果看子类(运行类型)的具体实践,即调用方法时,按照从子类(运行类型)开始查找方法
animal.eat();
animal.run();
animal.show();
animal.sleep();
// 希望可以调用 Cat的抓老鼠方法
// 多态的向下转型
// 语法: 子类类型 引用名 = (子类类型) 父类引用
Cat cat = (Cat) animal;
cat.catchMouse();
// 要求父类的引用必须指向的是当前目标类型的对象
// Dog dog = (Dog) animal; 不可以
}
}
属性没有重写之说,属性的值看编译类型
package com.hmk.poly.detail;
public class PolyDetail02 {
public static void main(String[] args) {
// 属性没有重写之说,属性的值看编译类型
Base base = new Sub(); // 向上转型
System.out.println(base.count);
Sub sub = new Sub();
System.out.println(sub.count);
}
}
class Base{
int count =10;
}
class Sub extends Base{
int count =20;
}
instance of比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wncYGnOJ-1668775946941)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221021153213845.png)]
java的动态绑定机制
// 动态绑定机制:
//1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
//2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
package com.hmk.poly.dynamic;
public class DynamicBinding {
public static void main(String[] args) {
// a的编译类型A 运行类型B
A a = new B(); //向上转型
System.out.println(a.sum());
System.out.println(a.sum1());
}
}
class A{//父类
// 动态绑定机制:
//1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
//2. 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
public int i = 10;
public int sum(){
//
return getI() + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
class B extends A{
public int i =20;
// public int sum() {
// return i + 20;
// }
public int getI(){
return i;
}
// public int sum1() {
// return i + 10;
// }
}
//30
//20
多态的应用
1)多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
应用实例:现有一个继承结构如下:要求创建一个Person对象、2个Student对象和2个Teacher对象,统一放在数组中,并调用say方法。
package com.hmk.poly.polyarr;
public class Person {
private String name;
private int age;
public Person(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;
}
public String say(){
return name + "\t" + age;
}
}
package com.hmk.poly.polyarr;
public class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
// 重写父类的say方法
@Override
public String say() {
return super.say() + " score=" + score;
}
}
package com.hmk.poly.polyarr;
public class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
// 重写父类的say方法
@Override
public String say() {
return super.say() + " 薪水= " + salary;
}
}
package com.hmk.poly.polyarr;
public class PloyArray {
public static void main(String[] args) {
// 应用实例:现有一个继承结构如下:要求创建一个Person对象
// 2个Student对象和2个Teacher对象,统一放在数组中,并调用say方法。
Person person[] = new Person[5];
person[0] = new Person("jack",20);
person[1] = new Student("guotu",22,100);
person[2] = new Student("chenxu",23,98);
person[3] = new Teacher("laozhang",58,5800);
person[4] = new Teacher("laowang",48,6000);
//循坏遍历多态数组,调用say
for (int i = 0; i < person.length ; i++) {
// person[i] 编译类型是 Person
System.out.println(person[i].say()); //动态绑定数组
}
}
}
应用实例升级:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study 怎么调用?
Student
// 特有方法
public void study(){
System.out.println("学生 " + getName() + " 正在学java...");
}
Teacher
// 特有方法
public void teach(){
System.out.println("老师 " + getName() + "正在教Java...");
}
package com.hmk.poly.polyarr;
public class PloyArray {
public static void main(String[] args) {
// 应用实例:现有一个继承结构如下:要求创建一个Person对象
// 2个Student对象和2个Teacher对象,统一放在数组中,并调用say方法。
Person person[] = new Person[5];
person[0] = new Person("jack",20);
person[1] = new Student("guotu",22,100);
person[2] = new Student("chenxu",23,98);
person[3] = new Teacher("laozhang",58,5800);
person[4] = new Teacher("laowang",48,6000);
//循坏遍历多态数组,调用say
for (int i = 0; i < person.length ; i++) {
// person[i] 编译类型是 Person
System.out.println(person[i].say()); //动态绑定数组
// 如何调用子类特有的方法,
// 比如Teacher有一个teach,Student有一个study 怎么调用?
// 向下转型
if(person[i] instanceof Student)
((Student) person[i]).study();
else if (person[i] instanceof Teacher)
((Teacher) person[i]).teach();
else if (person[i] instanceof Person)
System.out.println("不做处理");
else System.out.println("你的类型有误,请检查");
}
}
}
/*
jack 20
不做处理
guotu 22 score=100.0
学生 guotu 正在学java...
chenxu 23 score=98.0
学生 chenxu 正在学java...
laozhang 58 薪水= 5800.0
老师 laozhang正在教Java...
laowang 48 薪水= 6000.0
老师 laowang正在教Java...
*/
== 引用类型 判断的是地址
equals方法
equals:是Object类中的方法,只能判断引用类型
默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Inter,String
Integer interger1 = new Integer(1000);
Integer interger2 = new Integer(1000);
System.out.println(interger1 == interger2); //false
System.out.println(interger1.equals(interger2)); //true
String string1 = new String("hmk");
String string2 = new String("hmk");
System.out.println(string1 == string2); //false
System.out.println(string1.equals(string2)); //true
重写Object equals方法
package com.hmk.object;
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("jack",22,'男');
Person person2 = new Person("jack",22,'男');
System.out.println(person1.equals(person2));
}
}
class Person{ // extends Object
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
// 重写 equals方法
public boolean equals(Object obj){
if (this == obj){
return true;
}
if (obj instanceof Person) {
Person p =(Person)obj;
return this.name == p.name && this.age== p.age && this.gender == p.gender; // this.name.equals(p.name)
}
return false;
}
}
// true
hashCode方法
hashCode
public int hashCode()
返回对象的哈希码值。支持这种方法是为了散列表,如
HashMap
提供的那样 。
hashCode
的总合同是:
- 只要在执行Java应用程序时多次在同一个对象上调用该方法,
hashCode
方法必须始终返回相同的整数,前提是修改了对象中equals
比较中的信息。 该整数不需要从一个应用程序的执行到相同应用程序的另一个执行保持一致。- 如果根据
equals(Object)
方法两个对象相等,则在两个对象中的每个对象上调用hashCode
方法必须产生相同的整数结果。- 不要求如果两个对象根据
equals(java.lang.Object)
方法不相等,那么在两个对象中的每个对象上调用hashCode
方法必须产生不同的整数结果。 但是,程序员应该意识到,为不等对象生成不同的整数结果可能会提高哈希表的性能。尽可能多的合理实用,由类别
Object
定义的hashCode方法确实为不同对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但Java的编程语言不需要此实现技术。)
结果
该对象的哈希码值。
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
两个引用,如果指向的是不同对象,则哈希值是不一样的
哈希值主要是根据地址号来的!不能完全将哈希值等价于地址
后面在集合中,hashCode如果需要的话,也会重写。
package com.hmk.object;
public class HashCode {
public static void main(String[] args) {
AA aa = new AA();
AA aa1 = new AA();
AA aa3 = aa;
System.out.println(aa.hashCode());
System.out.println(aa1.hashCode());
System.out.println(aa3.hashCode());
}
}
class AA {
}
toString()
返回对象的字符串表示形式。一般来说, toString
方法返回一个“textually代表”这个对象的字符串。结果应该是一个简明扼要的表达,容易让人阅读。建议所有子类覆盖此方法。
该toString
类方法Object
返回一个由其中的对象是一个实例,该符号字符的类的名称的字符串
@` ”和对象的哈希码的无符号的十六进制表示。 换句话说,这个方法返回一个等于下列值的字符串:
getClass().getName() + '@' + Integer.toHexString(hashCode())
-
结果
对象的字符串表示形式。
重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式.
当直接输出一个对象时,toString 方法会被默认的调用,比如
System.out.println(monster);就会默认调用monster.toString()
package com.hmk.object;
import com.hmk.poly.Master;
public class ToString_ {
public static void main(String[] args) {
Monster monster = new Monster("guoyu","董事长",1000.0);
System.out.println(monster.toString());
System.out.println(monster);
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
// 重写toString
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
/*
Monster{name='guoyu', job='董事长', sal=1000.0}
Monster{name='guoyu', job='董事长', sal=1000.0}
*/
finalize方法垃圾回收
类变量 类方法
package chap10;
public class ChildGame {
public static void main(String[] args) {
Child zhao = new Child("zhao");
zhao.count++;
Child yang = new Child("yang");
yang.count++;
Child wang = new Child("wang");
wang.count++;
System.out.println(zhao.count);
}
}
class Child{
private String name;
//定义一个变量 count ,是一个类变量(静态变量)static 静态
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join(){
System.out.println(name + " 加入了游戏");
}
}
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
定义语法
访问修饰符 static 数据类型 变量名;
static 访问修饰符 数据类型 变量名;
访问类变量
类名.类变量名 或 对象名.类变量名 【静态变量的访问修饰符的访问权限和普通属性是一样的】
推荐 : 类名.类变量名
package chap10;
public class VisitStatic {
public static void main(String[] args) {
// 类名.类变量名
//说明:类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
System.out.println(A.name);
A a = new A();
//通过对象名.类变量名
System.out.println(a.name);
}
}
class A{
// 类变量
// 类变量的访问,必须遵守 相关的访问权限
public static String name = "play basketball";
}
类变量使用细节
什么时候使用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student (name,fee)
类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但java设计者推荐使用类名.变量名方式访问(前提 满足访问修饰符的访问权限和范围)
实例变量不能通过 类名.类变量名 方式访问
类变量是在类加载时就初始化了,即使你没有创建对象,只要类加载了,就可以使用类变量
类变量的声明周期是随类消亡而销毁
类方法基本介绍
-
类方法也叫静态方法
-
形式:
-
访问修饰符 static 数据返回类型 方法名(){ } 推荐
-
static 访问修饰符 数据返回类型 方法名(){ }
package chap10;
public class StaticMethods {
public static void main(String[] args) {
Stu merry = new Stu("merry");
Stu.payFee(100.0);
Stu.showFee();
}
}
class Stu{
private String name;//普通成员
//定义一个静态变量 来累计学生的学费
private static double fee = 0;
public Stu(String name) {
this.name = name;
}
//1. 当方法使用了static修饰后,该方法就是静态方法
//2. 静态方法就可以访问静态属性/变量
public static void payFee(double fee){
Stu.fee += fee;
}
public static void showFee(){
System.out.println("总学费有:" + Stu.fee);
}
}
类方法经典使用场景
-
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
-
比如:工具类中的 utils
Math类,Array类、Collections 集合类看下源码
-
在程序开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象,就可以使用了,比如打印以为数组、冒泡排序,完成某个计算任务等。
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this的参数
- 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名
静态方法只能访问静态成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)。
package chap10;
public class StaticExercise {
public static void main(String[] args) {
System.out.println(Person.getTotalPerson());
Person p1 = new Person();
System.out.println(Person.getTotalPerson());
}
}
class Person{
private int id;
private static int total = 0;
public static int getTotalPerson(){
return total;
}
public Person(){
total++ ;
id = total;
}
}
main方法语法
- main方法 虚拟机调用
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- java虚拟机在执行main()方法时,不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- java 执行的程序 参数1 参数2 参数3
public class Hello {
public static void main(String[] args) {
for (int i = 0; i < args.length ; i++) {
System.out.println(args[i]);
}
}
}
C:\JetBrains\JavaProject\chaper08\src\chap10>java Hello tom guoyu
tom
guoyu
1)在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
public class Main01 {
//静态的变量/属性
private static String name = "韩顺平教育";
//非静态的变量/属性
private int n1 = 10000;
//静态方法
public static void hi() {
System.out.println("Main01的 hi方法");
}
//非静态方法
public void cry() {
System.out.println("Main01的 cry方法");
}
public static void main(String[] args) {
//可以直接使用 name
//1. 静态方法main 可以访问本类的静态成员
System.out.println("name=" + name);
hi();
//2. 静态方法main 不可以访问本类的非静态成员
//System.out.println("n1=" + n1);//错误
//cry();
//3. 静态方法main 要访问本类的非静态成员,需要先创建对象 , 再调用即可
Main01 main01 = new Main01();
System.out.println(main01.n1);//ok
main01.cry();
}
}
final的基本使用
抽象类
用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名 {
}
用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体
抽象类的价值更多的作用是在于设计,是设计者设计好后,让子类继承实现抽象类
抽象类使用细节
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法。
- 一旦包含了abstract方法,则这个类必须声明为abstract
- abstract只能修饰类和方法,不能修饰属性和其他的
- 抽象类可以有任意的成员【抽象类还是类】
- 抽象方法不能有主体,既不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
- 抽象方法不能使用private、final、和static来修饰,因为这些关键字都是和重写相违背的
public class AbstractDetail01 {
public static void main(String[] args) {
// 抽象类,不能被实例化
// new A(); ❌
}
}
abstract class A{
public void hi(){
System.out.println("hi");
}
}
// 一个类一旦包含了abstract方法, 则这个类必须声明为abstract
abstract class B{
public abstract void hi();
}
练习
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary.提供必要的构造器和抽象方法:work()。对于Manager类来说,他既是员工还具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示“经理/普通员工 名字 工作中”
package abstract_;
public class AbstractExercise {
public static void main(String[] args) {
Manager jack = new Manager("jack", 999, 5000.0);
jack.setBonus(5000.0);
jack.work();
CommonEmployee tom = new CommonEmployee("tom", 888, 2000);
tom.work();
}
}
package abstract_;
public class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工" + getName() + " 工作中");
}
}
package abstract_;
abstract public class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
// 将work做成一个抽象方法
public abstract void work();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
package abstract_;
public class Manager extends Employee{
private double bonus;
public Manager(String name, int id, double salary) {
super(name, id, salary);
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理" + getName() + " 工作中");
}
}
抽象模板模式
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
语法:
interface 接口名{
//属性
//方法
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法
}
- 在jdk7前 接口里的所有方法都没有方法体
- jdk8后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
package interface_;
public interface AInterface {
// 写属性
public int n1 = 10;
//写方法 (抽象方法)
//在接口中,抽象方法可以省略 abstract关键字
public void hi();
// 在jdk8后,可以有默认实现方法,需要使用default关键字修饰
default void ok(){
System.out.println("ok...");
}
// 在jdk8后,可以有静态方法
public static void cry(){
System.out.println("cry...");
}
}
package interface_;
public class Interface02 {
public static void main(String[] args) {
A a = new A();
a.hi();
a.ok();
}
}
//1,如果一个类 implements实现 接口
//2,需要将该接口的所有抽象方法都实现
class A implements AInterface{
@Override
public void hi() {
System.out.println("hi...");
}
}
输出:
hi...
ok...
2个程序员,编写两个类,分别完成对mysql,oracle数据库的连接、关闭
接口
package interface_;
public interface DBInterface {
public void connect();//连接方法
public void close();// 关闭方法
}
mysql 类实现接口
package interface_;
public class MysqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接Mysql");
}
@Override
public void close() {
System.out.println("关闭mysql");
}
}
oracle类实现接口
package interface_;
public class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接Oracle");
}
public void close(){
System.out.println("关闭Oracle");
}
}
package interface_;
public class Interface03 {
public static void main(String[] args) {
MysqlDB mysqlDB = new MysqlDB();
OracleDB oracleDB = new OracleDB();
t(mysqlDB);
t(oracleDB);
}
public static void t(DBInterface db){
db.connect();
db.close();
}
}
/*输出:
连接Mysql
关闭mysql
连接Oracle
关闭Oracle*/
接口的注意事项与细节
- 接口不能被实例化
- 接口中所有的方法是public方法,接口中抽象方法,可以不用abstract修饰
- 一个普通类实现接口,就必须将该接口的所有方法实现
- 抽象类实现接口,可以不用实现接口的方法
- 一个类同时可以实现多个接口
- 接口中的属性,只能是final的,而且是public static final 修饰符。int a =1;实际上是public static final int a = 1;(必须初始化)
- 接口中属性的访问形式:接口名.属性名
- 一个接口不能继承其他的类,但是可以继承多个别的接口
- 接口的修饰符 只能是public和默认,这点和类修饰符是一样的
package interface_;
public class InterfaceDetail02 {
public static void main(String[] args) {
// 接口中的属性是 public static final
System.out.println(IB.n1); //说明n1 就是static
//IB.n1=30; 说明n1 是 final
}
}
interface IB{
// 接口中的属性,只能是final的,而且是public static final 修饰符
int n1 = 10; // 等价 public static final int n1 =10;
void hi();
}
interface IC{
void say();
}
// 接口不能继承其他的类,但是可以继承多个别的接口
interface ID extends IB,IC{
}
// 一个类可以同时实现多个接口
class AAA implements IB,IC{
public void hi(){
System.out.println("hi...");
}
public void say(){
System.out.println("say...");
}
}
//接口的修饰符 只能是public和默认 interface : IE.java
public interface IE{
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGiHOO0i-1668775946942)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221025214053866.png)]
接口多态性
多态参数
编写一个方法,计算机工作
形参是接口类型 实现了接口的类的对象可以传进来
public class Compute{
public void work(UsbInterface usbInterface){
// 通过接口,来调用方法
usb.Interface.start();
usb.Interface.stop();
}
}
接口类型的变量 可以指向 实现了接口的对象实例
多态数组
给Usb数组中,存放Phone 和 相机对象,Phone类还有一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb定义的 接口方法外,还需要调用Phone 特有方法call.
package interface_;
public class InterfacePolyArr {
public static void main(String[] args) {
//多态数组 ——》 接口类型数组
Usb[] usb = new Usb[2];
usb[0] = new Phone_();
usb[1] = new Camera_();
/*给Usb数组中存放Phone 和相机对象 ,phone类中还有一个特有的方法call()
* 请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone 特有方法
*/
for (int i = 0; i < usb.length; i++) {
usb[i].work(); //动态绑定
if (usb[i] instanceof Phone_){
((Phone_) usb[i]).call();
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb{
public void call(){
System.out.println("手机可以打电话。。。");
}
@Override
public void work() {
System.out.println("手机工作中");
}
}
class Camera_ implements Usb{
@Override
public void work() {
System.out.println("相机工作中");
}
}
多态传递
package interface_;
/*
*演示多态传递
*/
public class InterfacePolyPaa {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
IH ih = new Teacher();
// 如果IG 继承了 IH接口,而Teacher 类实现了IG接口
// 那么,实际上就相当于Teacher 类也实现了IH接口
//这就是所谓的接口多态传递现象
}
}
interface IH{ }
interface IG extends IH{}
class Teacher implements IG{
}
package interface_;
public class InterfaceExercise66 {
}
interface Aa{
int x =0;
}
class B{
int x =1;
}
class C extends B implements Aa{
public void pX(){
// System.out.println(x); //错误 不明确的x
// 访问接口的x就是用Aa.x
// 访问父类的x 就用super.x
System.out.println(Aa.x + " " +super.x);
}
public static void main(String[] args) {
new C().pX();
}
}
内部类
基本介绍:
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类被称为外部类(outer class)。是我们类的第五大成员。内部类的最大特点就是可以直接访问私有属性,并可以体现类与类之间的包含关系。
基本语法
class Outer{//外部类
class Inner{ //内部类
}
}
class Other{//外部其他类
}
内部类的分类
- 定义在外部类局部位置上(比如方法中)
- 局部内部类(有类名)
- 匿名内部类(没有类名 !!!)
- 定义在外部类的成员位置上
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类
定义在外部类的局部位置,比如方法中,并且有类名。
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量可以使用final
-
作用域:仅仅在方法和代码块中
-
局部内部类—访问—外部类的成员【访问方式:直接访问】
-
外部类—访问—局部内部类
访问方式: 创建对象,再访问(必须在作用域内)
-
外部其他类—不能访问—局部内部类(因为局部内部类地位是一个局部变量)
-
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员可以使用外部类名.this.成员 去访问
package innerclass;
/**
* 演示局部内部类
* */
public class LocalInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
}
}
class Outer02{//外部类
private int n1 = 100;
private void m2(){
System.out.println("Outer02 m2()");
}//私有方法
public void m1(){//方法
//局部内部类是定义在外部类的局部位置,通常在方法
//不能添加访问修饰符,可以使用final修饰
class Inner02{//局部内部类(本质仍然是一个类)
private int n1 = 800;
//可以直接访问外部类的所有成员,包含私有的
public void f1(){
// 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员可以使用外部类名.this.成员 去访问
// Outer02.this 本质就是外部类的对象,哪个对象调用了m1,Outer02.this就是哪个对象
System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
m2();
}
}
//外部类在方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类
本质是类 内部类 该类没有名字 同时还是一个对象
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
-
匿名内部类的基本语法
new 类或接口(参数列表){ 类体 };
package innerclass;
/**
* 演示匿名内部类的使用
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04{
private int n1 = 10;
public void method(){
IA tiger = new IA() { //基于接口的匿名内部类
@Override
public void cry() {
System.out.println("老虎哭了,呜呜呜...");
}
};
//jdk底层在创建匿名内部类 Outer04$1,立马创建了 Outer04$1实例,并把地址返回给tiger
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();
//基于类的匿名内部类
// 运行类型 内部类 Outer04$2
/*
* class Outer04$2 extends Father{}
*/
Father f1 = new Father("jack"){
@Override
public void test() {
System.out.println("重写test方法");
}
};
System.out.println("father对象的运行类型=" + f1.getClass());
f1.test();
//基于抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("狗狗吃东西");
}
};
animal.eat();
}
}
interface IA{
public void cry();
}
class Father{
public Father(String name){
}
public void test(){
}
}
abstract class Animal{
abstract void eat();
}
/*
tiger的运行类型=class innerclass.Outer04$1
老虎哭了,呜呜呜...
father对象的运行类型=class innerclass.Outer04$2
重写test方法
狗狗吃东西*/
package innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
}
}
class Outer05{
private int n1 = 99;
public void f1(){
//创建一个基于类匿名内部类
// Person p = new Person(){
// @Override
// public void hi() {
// System.out.println("匿名内部类重写了 hi方法");
// }
// };
// p.hi();//动态绑定
//直接调用
new Person(){
@Override
public void hi() {
System.out.println("匿名内部类重写了 hi方法");
}
@Override
public void ok(String str) {
super.ok(str);
}
}.ok("jack");
}
}
class Person{
public void hi(){
System.out.println("Person hi()");
}
public void ok(String str){
System.out.println("Person ok()" + str);
}
}
匿名内部类的最佳实践
当参数直接传递,简洁高效。
package innerclass;
public class InnerClassExercise {
public static void main(String[] args) {
//当作实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画...");
}
});
// 传统方法
f1(new Picture());
}
// 静态方法,形参是接口类型
public static void f1(IL il){
il.show();
}
}
//
interface IL{
void show();
}
//类--->实现IL
class Picture implements IL{
@Override
public void show() {
System.out.println("你好");
}
}
package innerclass;
import com.hmk.modifier.B;
public class InnerClassExercise01 {
public static void main(String[] args) {
/*
* 有一个铃声接口Bell,里面有个ring方法
* 有一个手机类CellPhone,具有闹钟功能alarmclock,参数是Bell类型
* 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印懒猪起床了
* 在传入另一个匿名内部类打印 小伙伴上课了*/
CellPhone cellPhone = new CellPhone();
//传递的是实现了 Bell接口的匿名内部类
cellPhone.alarmClock(new Bell(){
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴上课了");
}
});
}
}
interface Bell{//接口
void ring();//方法
}
class CellPhone{//类
public void alarmClock(Bell bell){//形参是Bell接口类型
System.out.println(bell.getClass());
bell.ring();//动态绑定
}
}
成员内部类
成员内部类是定义在外部类的成员位置
- 成员内部类是定义在外部类的成员位置,并且没有static修饰
- 可以直接访问外部类的所有成员,包含私有的
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
- 作用域和外部类的其他成员一样,为整个类体,在外部类的成员方法中创建成员内部类对象,在调用方法
- 成员内部类—访问—>外部类 直接访问
- 外部类—访问—>内部类 创建对象再访问
- 外部其他类—访问—>成员内部类
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员可以使用外部类名.this.成员 去访问
package innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的2种方式
//相当于把new Inner08() 当作是outer08的成员
//1.第一种方式
Outer08.Inner08 inner08 = outer08.new Inner08();
//2.第二种 在外部类中编写一个方法,可以返回Inner08对象
Outer08.Inner08 inner08t = outer08.getInner08Instance();
inner08t.say();
}
}
class Outer08{
private int n1 = 10;
public String name = "张三";
// 成员内部类是定义在外部类的成员位置,并且没有static修饰
class Inner08{
private int n1 =66;
public void say(){
System.out.println("n1=" + n1 + " name=" + name + " 外部类n1=" + Outer08.this.n1);
}
}
//写方法
public void t1(){
Inner08 inner08 = new Inner08();
inner08.say();
}
//2.返回一个Inner08实例
public Inner08 getInner08Instance(){
return new Inner08();
}
}
静态内部类
静态内部类是定义在外部类的成员位置,并且用static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能访问非静态成员
- 可以添加任意访问修饰符,因为它的地位就是一个成员
- 作用域:同其他的成员,为整个类体
- 静态内部类—访问—>外部类(静态属性,直接访问所有静态成员)
- 外部类—访问—>静态内部成员 (创建对象—访问)
- 如果外部类和静态内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员可以使用外部类名.成员 去访问
package innerclass;
public class StaticInnerClass {
public static void main(String[] args) {
//外部其他类访问静态内部类
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//编写一个方法,可以返回静态内部类的对象实例
Outer10 outer10 = new Outer10();
Outer10.Inner10 inner10_ = outer10.getInner10_();
inner10_.say();
}
}
class Outer10{
private int n1 = 10 ;
private static String name = "张三";
private static void cry(){}
static class Inner10{
public void say(){
System.out.println(name);
cry();
}
}
public Inner10 getInner10_(){
return new Inner10();
}
}
异常
基本概念
java语言中,将程序执行过程中发生的不正常情况称为“异常”。开发过程中的语法错误和逻辑错误不是异常
执行过程中所发生的异常事件可分为两类
Error(错误):java虚拟机无法解决的严重问题。如JVM系统内部错误、资源耗尽等严重情况。比如StackOverFlow[栈溢出]和OOM(out of memory),Error是严重错误,程序会崩溃
Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等。Exception分为两大类:运行时异常[]和编译时异常[]。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WQD5XTUE-1668775946943)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221102184643037.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOkYuTjr-1668775946944)(C:\Users\vampire\AppData\Roaming\Typora\typora-user-images\image-20221102184629638.png)]
运行时异常,编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常java.lang.RuntimeException类及它的子类都是运行时异常,可以不做处理,这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。编译时异常,是编译器要求必须处置的异常。
运行时异常
空指针异常
package com.hmk.exception_;
public class NullPointerException_ {
public static void main(String[] args) {
String name = null;
//空指针异常
System.out.println(name.length());
}
}
数字格式不正确异常
package com.hmk.exception_;
public class NumberFormatException {
public static void main(String[] args) {
//NumberFormatException数字格式不正确异常
//当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
//使用异常我们可以确保输入是满足条件数字
String name = "人之初";
int num = Integer.parseInt(name);
}
}
数组下标越界异常
package com.hmk.exception_;
public class ArrayIndexOutOfBoundsException {
public static void main(String[] args) {
//ArrayIndexOutOfBoundsException 数组下标越界异常
//用非法索引访问数组时抛出异常。如果索引为负或大于等于数组大小,则该索引为非法索引
int arr[] ={1,2,4};
for (int i = 0; i <= arr.length ; i++) {
System.out.println(arr[i]);
}
}
}
类型转换异常
package com.hmk.exception_;
public class ClassCastException {
public static void main(String[] args) {
//ClassCastException 类型转换异常
//当试图将对象强制转换为不是实例的子类时,抛出该异常。
A b = new B();//向上转型
B b2 = (B)b;//向下转型
C b3 = (C)b;//爬出ClassCastException
}
}
class A{ }
class B extends A{}
class C extends A{}
数学运算异常
编译时异常
编译异常是指在编译期间就必须处理的异常,否则代码不能通过编译
常见的编译异常
- SQLExecption 操作数据库时,查询表可能发生异常
- IOException 操作文件时,发生的异常
- FileNotFoundException 当操作一个不存在的文件时,发生异常
- ClassNotFoundException 加载类,而该类不存在时,异常
- EOFException 操作文件,到文件末尾,发生异常
- IllegalArguementException 参数异常
异常处理
异常处理就是当异常发生时,对异常处理的方式
- try-catch-finally
程序员在代码中捕获异常,自行处理
- throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
try-catch异常处理
-
java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch用于处理try中发生的异常。可以根据需要在程序中有多个try…catch块。子类异常放在前面,父类异常放在后面。
-
基本语法
try{ //可疑代码 //将异常生成对应的异常对象,传递给catch块 }catch(异常){ //对异常的处理 }
-
可以进行try-finally配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉。应用场景,就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。
try{ //代码 } finally{//总是执行 }
package com.hmk.exception_;
public class TryFinally {
public static void main(String[] args) {
try {
int n1 = 10;
int n2 = 0;
System.out.println(n1/n2);
}finally {
System.out.println("执行finally.."); //执行finally块语句 直接退出 不会执行下面的语句
}
System.out.println("程序继续执行");
}
}
package com.hmk.exception_;
public class Exercise01 {
public static int method() {
try {
String[] names = new String[3];
if (names[1].equals("tom")) {
System.out.println(names[1]);
} else {
names[3] = "hmk";
}
return 1;
} catch (java.lang.ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {
return 3;
} finally {
return 4;
}
}
public static void main(String[] args) {
System.out.println(method());
}
}
//4
package com.hmk.exception_;
public class Exercise02 {
public static int method(){
int i =1;
try {
i++;
String[] names = new String[3];
if (names[1].equals("tom")){ //空指针
System.out.println(names[1]);
}else {
names[3] = "hmk";
}
return 1;
}catch(java.lang.ArrayIndexOutOfBoundsException e){
return 2;
}catch (NullPointerException e){
return ++i; // i=3 保存临时变量temp=3
}finally {
i++;
System.out.println("i=" + i);
}
}
public static void main(String[] args) {
System.out.println(method());
}
}
try-catch-finally
- 如果没有出现异常,则执行try块中的所有语句,不执行catch块中语句,如果有finally,最后还需要执行finally里面的语句
- 如果出现异常,则try块中异常发生后,剩下的语句不再执行。将执行catch块中的语句,如果有finally,最后还需要执行finally里面的语句。
throws异常处理
- 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法地调用者负责处理。
- 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
package com.hmk.exception_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Throws01 {
public static void main(String[] args) {
}
//throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。既可抛出异常
public void f2() throws FileNotFoundException { //Exception
//创建了一个文件流对象
//这里的异常是一个FileNotFoundException 编译异常
//1。使用前面见过的 try-catch-finally
//2.使用throws,抛出异常,让调用f2方法的调用者(方法)处理
FileInputStream fis = new FileInputStream("d://aa.txt");
}
}
throws异常处理使用细节
- 对于编译异常,程序必须处理,比如try-catch或者throws
- 对于运行异常,程序中如果没有处理,默认就是throws的方式处理
- 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
- 在throws过程中,如果有方法try-catch,就相当于处理异常,就可以不必throws
class Father {
public void method() throws RuntimeException{
}
}
class Son extends Father {
//子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
// 所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
@Override
public void method() throws NullPointerException{
}
}
public static void f1() throws FileNotFoundException{//抛出异常
//这里调用f3报错
// 因为f3方法抛出的是一个编译异常
// 这时,需要f1() 必须处理这个编译异常 或者try-catch
f3();
}
public static void f3() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
public static void f4(){
//java中,并不要求程序员显示处理,因为有默认处理机制
f5();//没有错 因为f5()方法中抛出的时运行异常
}
public static void f5() throws ArithmeticException{
}
自定义异常
当程序中出现了某些“错误”,但该错误信息,并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述错误信息
自定义异常的步骤
- 定义类: 自定义异常类名(程序员自己写)继承exception或RuntimeException
- 如果继承Exception,属于编译异常
- 如果RuntimeException,属于运行异常(一般来说,继承RuntimeException)
自定义异常应用实例
当我们接收Person对象年龄时,要求范围在18-120之间,否则抛出一个自定义异常(要求 继承RuntimeException),并给出提示信息
package com.hmk.exception_;
public class CustomException {
public static void main(String[] args) {
int age =9;
//要求范围在18-120之间,否则抛出一个自定义异常
if (!(age >=18 && age <=120)){
throw new AgeException("年龄需要在 18~120之间");
}
System.out.println("你的年龄范围正确");
}
}
class AgeException extends RuntimeException{
public AgeException(String message) {
super(message);
}
}
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
本章作业
编写应用程序EcmDef.java,接收命令行的两个参数(整数)。计算两数相除。
计算两个数相除,要求使用cal(int n1, int n2)
对数据格式不正确(NumberformatException)、缺少命令行参数(ArrayIndexOutofBoundsException)、除0 进行异常(ArithmeticException)处理
package com.hmk.exception_;
public class Homework01 {
public static void main(String[] args) {
/*
* 编写应用程序EcmDef.java,接收命令行的两个参数(整数)。计算两数相除。计算两个数相除,要求使用cal(int n1, int n2)
* 对数据格式不正确(NumberformatException)、缺少命令行参数(ArrayIndexOutofBoundsException)、除0 进行异常(ArithmeticException)处理*/
//先验证输入的参数的个数对不对
try {
if (args.length !=2){
throw new java.lang.ArrayIndexOutOfBoundsException("参数个数不对");
}
//先把接收到的参数,转成整数
int n1 = Integer.parseInt(args[0]);
int n2 = Integer.parseInt(args[1]);
double res = cal(n1,n2);//该方法可能抛出ArithmeticException
System.out.println(res);
} catch (java.lang.ArrayIndexOutOfBoundsException e) {
System.out.println(e.getMessage());;
}catch (java.lang.NumberFormatException e){
System.out.println("参数格式不正确,需要输入整数");
}catch(ArithmeticException e){
System.out.println("出现了除0的异常");
}
}
//编写cal方法,两个数的商
public static double cal(int n1, int n2) {
return n1 / n2;
}
}
枚举类
把具体的对象一个一个例举出来的类就称为枚举类
枚举是一组常量的集合
枚举属于一种特殊的类,里面只包含一组有限的特定的对象
枚举的两种实现方式
- 自定义枚举类
- 使用enum关键字实现枚举
自定义枚举类
public class Enumeration02 {
public static void main(String[] args) {
System.out.println(Season.SPRING);
System.out.println(Season.WINTER.toString());
}
}
//演示自定义枚举
class Season { //类
private String name;
private String desc; //描述
//定义了四个对象,固定
public static final Season SPRING = new Season("春天","温暖");
public static final Season WINTER = new Season("冬天","寒冷");
public static final Season AUTUMN = new Season("秋天","凉爽");
public static final Season SUMMER = new Season("夏天","炎热");
// 将构造方法私有化,防止直接new
// 去掉setXxx方法,防止属性被修改
// 在Season内部,直接创建固定的对象
// 优化,可以加入final修饰符
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
构造器私有化
本类内部创建一组对象
对外暴露对象(通过为对象添加public final static 修饰符)
可以提供get方法,但是不要提供set方法
使用enum关键字实现枚举
package com.hmk.enum_;
public class Enumberation03 {
public static void main(String[] args) {
System.out.println(Season2.SPRING);
System.out.println(Season2.What);
}
}
//演示自定义枚举
enum Season2 { //类
//定义了四个对象,固定
// public static final Season SPRING = new Season("春天","温暖");
// public static final Season WINTER = new Season("冬天","寒冷");
// public static final Season AUTUMN = new Season("秋天","凉爽");
// public static final Season SUMMER = new Season("夏天","炎热");
// 如果使用了enum来实现枚举类
//1.使用关键字 enum 代替class
//2.public static final Season SPRING = new Season("春天","温暖"); 直接使用
// SPRING("春天","温暖") 常量名(实参列表)
//3.如果有多个常量(对象),使用,间隔
//4.如果使用enum 来实现枚举,要求将定义常量对象写在前面
SPRING("春天","温暖"),WINTER("冬天","寒冷"),
What;// 调用无参构造器
private String name;
private String desc; //描述
// 将构造方法私有化,防止直接new
// 去掉setXxx方法,防止属性被修改
// 在Season内部,直接创建固定的对象
// 优化,可以加入final修饰符
private Season2(String name, String desc) {
this.name = name;
this.desc = desc;
}
private Season2() {//无参构造器
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
枚举对象必须放在枚举类的行首。