前言
各位帅帅美美们好,这里是你们的c栈算法小辰哥,在旷课自学linux系统部分后,是时候应付一下学校的考试了,所以就自己整理了一下java来应付一下考试,顺便对这门传奇的语言有一个大概的认识,在阅读此博客前,请注意,java考试的重点是面向对象编程,对于流操作和文件操作,对于我们这种c栈的小伙伴以应付考试和大概了解的目的而言,是非重点,如果学校要考则背诵典型程序即可,集合多线程部分,尽请期待,备考部分也尽请期待,话不多说,我们现在开始!(正式讲解跳到面向对象,引言为我收集的觉得好的学校ppt)
引言
首先是java的数据类型:
整型:byte(8字节),short,int long(64位),全部是有符号类型,如果int要转换为byte或者short必须要显示转换
浮点型:float,double(老朋友)
字符类型:char
布尔类型:boolean(新朋友)
复合数据类型:class(类),interface(接口)
很神奇吧,记住就好
每次调用打印都默认换行
把数组换做对象,还有,我勒个范围for啊
1.面向对象
如果c++是将c改造为一门面向对象的语言,那java就是纯纯的面向对象,在一切的开始,先说明一下java的文件操作,每个文件都必须有一个主类,这个主类包含着文件的main函数,类名必须和文件名一致(去掉.java后缀),然后正式开始
2.一切皆对象
如果linux一切皆文件,那么java一切皆对象,一切操作都是找对象,建对象,用对象
首先就是类,和c++一样,都将具有相同属性的事物放在一个类中
然后就是对象,也和c++一样,是类的具象化,举个例子,比如我们的类是猫类,那么实例化的对象就是汤姆,香蕉猫,它们都是猫,具有猫的特征,也拥有自己的特有属性
3.创建类
class ClassName {
成员变量/ 实例变量;
成员方法;
}
首先没有结尾的;,c++写惯的宝宝注意一下,要不然满页飘红
再来解释一下成员变量,就是该类的属性,成员方法就是该类的函数
class Cat
{
String name;
int age;
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
来说一下细节,类名首字母必须大写(第一次遇到如此强调大驼峰命名法的语言),然后是成员变量name和age,以及一个成员函数和show,注意java的打印是System.out.println(所有要打印的变量或者字符串,用+来连接)
4.实例化对象
和c++一样,java用new关键字来初始化对象,储存在堆里面
格式:类名 引用名=new 类名()
Cat tom=new Cat();
和c++一样,用.来访问对象的属性和方法,一个类可以有多个对象
现在来实例化我们的Cat类
class Cat
{
String name;
int age;
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat();
tom.show();
tom.name="footballtom";
tom.age=3;
tom.show();
tom.eat("芝士汉堡");
}
}
这是我们的结果,可以看出来String字符串被初始化为null(按照c++中的nullptr来理解,不能使用),int类型被初始化为0
name:null,age:0
name:footballtom,age:3
小猫你可以吃芝士汉堡
5.构造方法
和c++的构造函数一样,java在实例化对象的时候需要调用构造函数,如果像刚才的Cat类一样没有给出构造函数,系统会给出默认构造函数,当然,你可以定义无参和有参的构造函数并重载
class Cat
{
String name;
int age;
public Cat()
{
System.out.println("无参构造");
}
public Cat(String s,int a)
{
name=s;
age=a;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat();
tom.show();
Cat banana=new Cat("banana",3);
banana.show();
}
}
我们可以实现构造函数的重载
无参构造
name:null,age:0
有参构造
name:banana,age:3
注意,java不支持类似以下的默认参数构造函数
public Cat(String s="banana",int a)
{
name=s;
age=a;
System.out.println("有参构造");
}
不能用实例对象来调用对象自己的构造函数,相当于自己又创建了一个新的自己
6.this关键字
再来看一下我们的猫猫类,改了一下我们的有参构造
class Cat
{
String name;
int age;
public Cat()
{
System.out.println("无参构造");
}
public Cat(String name,int age)
{
name=name;
age=age;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat();
tom.show();
Cat banana=new Cat("banana",3);
banana.show();
}
}
无参构造
name:null,age:0
有参构造
name:null,age:0
那么问题来了,我是不是初始化了个寂寞,看来形成和成员变量不能同名(java的就近匹配原则,编译器找最近的同名变量)
如果必须要使用同名变量,就在成员变量前面加个this
class Cat
{
String name;
int age;
public Cat()
{
System.out.println("无参构造");
}
public Cat(String name,int age)
{
this.name=name;
this.age=age;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat();
tom.show();
Cat banana=new Cat("banana",3);
banana.show();
}
}
结果正常了
无参构造
name:null,age:0
有参构造
name:banana,age:3
this关键字也可以调用自己的成员方法
void tes1()
{
this.show();
System.out.println("内部方法调用');
}
当成员函数被private修饰,我们就可以写一个成员函数来这样调用,进行一个叫封装的运动
this也能调用其他构造方法,配合部分参数构造函数,这种就是初始化一部分成员变量
public Cat(String a)
{
System.out.println("部分参构造");
}
public Cat(String a,int b)
{
this(Cat(a));
this.age=age;
System.out.println("有参构造");
}
this(参数)的格式可以调用其他构造方法
注意,this(参数)的构造方法必须放在调用它的构造函数的第一行,this构造方法不能互相调用(死循环),this是当前对象的引用(和python差不多的样子)
void te()
{
System.out.println(this);
}
会打印关于对象的信息,包名+类名编号(包以后聊)
7.封装
面向对象的三大特性:继承,封装,多态,现在来看看java中的权限修饰符
注意:首先protected一般在继承中使用(类似c++),在默认什么都不写时默认使用default权限,访问权限除了限制对象的权限,也限制类的权限,private不能直接修饰类(你搞了一个类,但是外部看不见更用不了,那你整这个类的意义是啥)
8.包
为了更好的管理类,把很多个类收集成为一组,称为包,在一个工程中可以存在同名类,但是不能位于相同包
导入包中的类:import +包
例子:
import com.text.Cat
在com.text也就是包名路径中调用我们的猫猫类
import java.util.Date
在java标准库util包中调用Date类
省略类名换成*表示该包之下所有的类都可以使用
可以使用import static导入包中静态的方法和字段
自定义包:
1.文件上加一条package语句说明代码在哪一个包中
2.包名尽量使用唯一的名字,常用形式为公司域j名的颠倒
3.包名要和代码路径相匹配
4.没有该语句默认存储在默认包中
常见包:
1.java.util:是java提供的常用包
2.java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入
3.java.lang.reflect:java 反射编程包
4.java.net:进行网络编程开发包
5.java.sql:进行数据库开发的支持包
6.ava.io:I/O编程开发包
9.static关键字
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的
静态成员变量属性:
1.所有对象共享,存储在独立空间中
2.可以通过对象名或类名访问,推荐类名访问
3.存储在方法区中
4.生命周期与类的生命周期一致
接下来是猫猫类演示
class Cat
{
String name;
int age;
static String sex;
public Cat()
{
System.out.println("无参构造");
}
public Cat(String name,int age)
{
this.name=name;
this.age=age;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat();
tom.sex="man";
System.out.println(tom.sex);
Cat.sex="woman";
System.out.println(Cat.sex);
}
}
结果如下
无参构造
man
woman
注意,类似Cat.sex=null是合法的,因为由类名直接访问所以没有对象也能调用,就不会有问题,因为本来也没有对象
不能在方法中定义static变量,函数存储在栈里面,static在方法区,也就是堆里面,这俩根本放不到一起
静态成员变量改装全局变量:在static后面加一个final,使类里面的静态成员变量升级为全局变量,让其他类也可以使用
与静态成员变量类似,函数前加一个static直接升级为静态方法,不属于某个对象,和静态成员变量一样,都可以通过对象和类名调用,推荐后者,不能在静态方法中调用非静态变量和非静态方法,无法重写实现多态(都静态了就别搞多态了吧)
非静态成员方法只能通过对象访问的方式来访问静态成员变量和静态方法
静态成员变量的初始化:
1.定义时赋值(就地初始化)
2.静态代码块初始化(所以,我们接下来的内容是......)
10.代码块
使用{}定义的一段代码被称为代码块,分为四种:
1.普通代码块
2.构造块
3.静态块
4.同步代码块(先不聊)
普通代码块:直接定义在方法中的代码块
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
构造代码块:定义在类中的代码块,一般用于初始化成员变量,构造块在对象构造时调用,每次构建时都被调用,优先于类构造函数执行
class Cat
{
String name;
int age;
{
System.out.println("构造代码块");
}
static String sex;
public Cat()
{
System.out.println("无参构造");
}
public Cat(String name,int age)
{
this.name=name;
this.age=age;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat("footballtom",20);
}
}
输出结果如下
构造代码块
有参构造
静态代码块:使用static定义的代码块,一般用于定义静态成员变量,无论多少对象只会执行一次,且优先于构造代码块执行
class Cat
{
String name;
int age;
{
System.out.println("构造代码块");
}
static String sex;
{
System.out.println("静态代码块");
}
public Cat()
{
System.out.println("无参构造");
}
public Cat(String name,int age)
{
this.name=name;
this.age=age;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
Cat tom=new Cat("footballtom",20);
}
}
结果如下
静态代码块
构造代码块
有参构造
主类中的static代码优先于主类执行
public class Text1//我的文件名是Text1
{
static
{
System.out.println("进入主类静态代码块");
}
public static void main(String[] args)//相当于c++里面的main函数
{
System.out.println("进入主方法");
}
}
结果如下
进入主类静态代码块
进入主方法
静态变量存在于方法区中,类定义时就会有初始值(在以下代码中,初始值为10),类此时在方法区中。但此时类只是定义了,还没被加载。当主方法中使用了该类时,就需要把该类从方法区加载到内存中。类加载后,静态代码块就被执行了(x = 10——> x = 100)
class St{
static int x = 10;
static {
x = 100;
System.out.println("St类的静态代码块");
}
public St(){
System.out.println("St类的无参构造");
}
}
public class Main{
public static void main(String[] args) {
St st = new St();
System.out.println(st.x);
}
}
结果如下
St类的静态代码块
St类的无参构造
100
总结:
1.静态代码无论有多少个对象只会执行一次
2.静态成员变量是类的属性,在加载类空间时初始化
3.类中有多个静态代码块,按定义顺序执行
4.实例代码块只在创建对象时执行
11.匿名对象
和c++一样,匿名对象表示没有名字的对象,没有引用的对象叫做匿名对象,匿名对象只在创建对象时使用,如果一个对象只用一次,之后就不考虑使用了,可以考虑使用匿名对象
new出来的对象没有引用指向,使用一次就被系统销毁,这种对象常用于测试如new Dog()
class Cat
{
String name;
int age;
{
System.out.println("构造代码块");
}
static String sex;
{
System.out.println("静态代码块");
}
public Cat()
{
System.out.println("无参构造");
}
public Cat(String name,int age)
{
this.name=name;
this.age=age;
System.out.println("有参构造");
}
void eat(String zhishihanbao)
{
System.out.println("小猫你可以吃"+zhishihanbao);
}
void show()
{
System.out.println("name:"+name+",age:"+age);
}
}
public class Text1//我的文件名是Text1
{
public static void main(String[] args)//相当于c++里面的main函数
{
new Cat("footballtom",20).show();
}
}
结果如下
构造代码块
有参构造
name:footballtom,age:20
12.打印对象
当一个引用类型的变量调用println函数打印时,默认输出的都是引用类型的地址。(不是真正的内存地址,Java中程序员是无法知道任何确认的内存地址)就和c++一样,打印指针的地址没有任何意义,要对其解引用
如果真的要打印,就要在类里面重写toString方法,模拟打印猫猫类
class Cat {
String name;
int age;
{
System.out.println("构造代码块");
}
public Cat() {
System.out.println("无参构造");
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造");
}
// 重写toString方法
@Override
public String toString() {
return "Cat{name='" + name + "', age=" + age + "}";
}
void eat(String food) {
System.out.println("小猫你可以吃" + food);
}
void show() {
System.out.println("name:" + name + ",age:" + age);
}
}
public class Text1 {
public static void main(String[] args) {
Cat myCat = new Cat("footballtom", 20);
System.out.println(myCat);
myCat.show();
}
}
结果如下
构造代码块
有参构造
Cat{name='footballtom', age=20}
name:footballtom,age:20
tostring方法会在println函数被调用时自动调用,完成序列化操作(对象转成字符串)
13.继承
java的继承和c++不一样,它只有单继承没有多继承
语法(使用extends关键字)
修饰符 class 子类 extends 父类 {
}
class Animal
{
String name;
void eat()
{
System.out.println(this.name+"正在吃好吃的");
}
}
class Dog extends Animal
{
void wof()
{
System.out.println(this.name+"正在狗叫");
}
}
class Cat extends Animal
{
void miao()
{
System.out.println(this.name+"正在喵喵叫");
}
}
和c++一样,java的继承会将父类的成员变量继承到子类中
成员访问:父子同名先在子类找,再在父类找,和c++大同小异
super关键字:
子类和父类中有同名的成员,那么如何在子类方法中访问父类成员呢?可以使用super关键字
(子类对父类给出的东西的修改与父类早已经存在的东西毫无关系)
class Base {
public int X,a,b,c;
public void method(){
System.out.println("父类有,但子类没有的方法");
}
public void method1(){
System.out.println("父类同名方法");
}
}
class Derived extends Base{
public int x = 99; // 与父类 X 不同名
public int a = 100; // 与父类中成员a同名,且类型相同
public double b = 101; // 与父类中成员b同名,但类型不同
//super.c
public void method1(){
System.out.println("子类同名方法");
}
public void test(){
super.a = 88; // super是获取到子类对象中从基类继承下来的部分,对原父类不产生影响
super.X = 77;
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
method1(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
super.method1();
System.out.println("父类的成员变量为" + super.X + ",子类的成员变量为" + x);
System.out.println("父类的成员变量为" + super.a + ",子类的成员变量为" + a);
System.out.println("父类的成员变量为" + super.b + ",子类的成员变量为" + b);
}
}
public class Test {
public static void main(String[] args) {
Derived derived = new Derived();
Base base = new Base();
derived.test();
System.out.println();
System.out.println("父类的成员变量为" + base.X + ",子类的成员变量为" + derived.x);
System.out.println("父类的成员变量为" + base.a + ",子类的成员变量为" + derived.a);
System.out.println("父类的成员变量为" + base.b + ",子类的成员变量为" + derived.b);
// 子类没有c,访问的肯定是从父类继承下来的c
System.out.println("父类的成员变量为" + base.c + ",子类的成员变量为" + derived.c);
base.method(); derived.method();
base.method1(); derived.method1();
}
}
结果如下
子类同名方法
父类同名方法
父类的成员变量为77,子类的成员变量为99
父类的成员变量为88,子类的成员变量为100
父类的成员变量为0.0,子类的成员变量为101.0
父类的成员变量为0,子类的成员变量为99
父类的成员变量为0,子类的成员变量为100
父类的成员变量为0,子类的成员变量为101.0
父类的成员变量为0,子类的成员变量为0
父类有,但子类没有的方法
父类有,但子类没有的方法
父类同名方法
子类同名方法
可得结论:
1.父类的静态代码优先于子类静态代码执行,且最早执行
2.父类实例代码块和父类构造方法紧接着执行
3.子类的实例代码块和子类构造方法紧接着执行
4.第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
继承方式
一般不会出现三层以上的继承关系,如果要截断继承,可以使用final关键字
final关键字:
首先是与上文相呼应的常量(不可修改)
格式:final 变量或字段
然后就是截断继承,被final修饰的类不能再被继承
格式:final 类
最后是修饰方法,表示该方法不能被重写
组合与继承:这一点和c++完全一样,组合就是类套类表示所属关系,子类父类代码不共享,属于黑盒式编程,可以面向接口编程(后面细说),而继承子类父类代码共享,属于白盒式编程
最后,让我们来总结一下继承
首先,继承实现了数据和方法的共享,提高了代码的复用性,可维护性和扩展性,但是也导致了耦合性,牵一发而动全身
14.多态
多态,简单来说就是方法和类具有多种状态,在c++里面就是函数重载,现在来聊聊java里面的多态部分
首先是多态的适用条件:
1.存在继承关系的类之间才能使用多态性,通常是父类通过变量引用子类对象来实现
2.子类必须重写父类的方法(类似纯虚函数)
3.使用父类的引用变量来引用子类对象
下面是猫猫和狗狗实现的多态
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃猫条");
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.eat();
animal2.eat();
}
}
结果如下
狗吃骨头
猫吃猫条
那么问题来了,啥是@Override,其实它叫重写
重写,也叫覆盖,是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法
现在先强调一些概念
1.首先是继承关系,不清晰的小伙伴可以看看上一个小标题
2.方法分为方法签名(名称,参数列表和返回值)和方法体({}之间的代码块),方法重写就是只改变方法块
3.@Override标明了重写的动作,编译器会自动核查是否满足条件
4.动态绑定,也就是父类通过实际引用动态绑定到需要调用的子类方法
接下来是重写规则:
1.重写之后的方法的方法签名不能变
2.如果非要改变返回类型,那只有返回被重写函数的子类类型,比如父类函数返回了一个Animal对象,而子类给它重写了一下返回了一个Cat对象
3.重写的访问权限只能大于等于被重写的访问权限
4.static private,final会禁止重写
5.重写方法@Override可以不写,它就是起到一个检查的作用(还是稍微勤快一点写上才是)
6.子类中的重写方法可以调用父类的重写方法,使用super关键字
重写和重载的区别(引言里面有一个非常牛的图,简单明了,可以看看)
重载的规则:
1.方法名称必须相同
2.参数列表必须不同,编译器靠参数数量,类型和顺序区分
3.返回值可以变可以不变
4.重载方法必须在同一个类中
5.重载方法可以有相同的权限修饰符与异常
重写的设计原则:对于已经开始使用的类,可以保留共性内容,添加新内容
静态绑定,前期绑定或者早绑定:根据用户传的实参确定调用方法,比如函数重载
动态绑定,后期绑定或者晚绑定:编译时无法确定,必须等到程序运行时才能确定,当调用对象方法时该方法会与对象的运行类型相绑定,而对象属性没有这个机制,哪里声明哪里使用
// 父类
class Animal {
public void makeSound() {
System.out.println("动物发出声音!");
}
}
// 子类
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫喵喵叫!");
}
}
// 演示动态绑定
public class DynamicBindingDemo {
public static void main(String[] args) {
// 向上转型(自动类型转换)
// 编译时,animal 被视为 Animal 类型
// 运行时,animal 指向的对象是 Cat 类型
Animal animal = new Cat();
// 调用 makeSound 方法
// 编译时,编译器只知道 animal 是 Animal 类型,因此只能看到 Animal 类中定义的 makeSound 方法
// 运行时,JVM 查找并调用实际对象(Cat 类型)的 makeSound 方法
// 这就是动态绑定的过程
animal.makeSound(); // 输出:猫喵喵叫!
}
}
总的来说:重载是在同一个类中根据参数列表的不同定义多个具有相同名称但参数不同的方法,而重写是子类重新定义和实现了从父类继承的方法。重载方法通过静态绑定在编译时确定调用,重写方法通过动态绑定在运行时确定调用。重载用于实现相似功能但具有不同参数的方法,重写用于改变父类方法的行为以适应子类的需要
多态的转型
首先是向上转型,就是父类引用子类对象
特点:
1.等号左边为编译类型,右边为运行类型
2.调用父类所有可调用成员
3.不能调用子类特有成员
4.运行效果看子类的具体实现
格式:父类类型 变量名=new 子类类型
优点:
1.使用父类的引用类型来调用子类重写的方法
2.编译更为通用的代码,使父类不需要知道子类的类型就可以使用子类的函数
缺点:不能调用子类的特有方法(非要调用就必须使用强转)
来看看我们猫猫类的例子
class Animal
{
void eat()
{
System.out.println("动物吃饭");
}
}
class Cat extends Animal
{
@Override
void eat()
{
System.out.println("猫猫吃猫条");
}
void miao()
{
System.out.println("猫猫喵喵叫");
}
}
public class Test
{
public static void main(void *args)
{
Animal mycat =new Cat();
mycat.eat();
void allin(Animal mycat)
{
mycat.eat();
}
allin(new Animal());
allin(new Cat());
mycat=(Cat)mycat;
mycat.miao();
}
}
结果如下
猫猫吃猫条
动物吃饭
猫猫吃猫条
猫猫喵喵叫
结果分析:首先测试Animal类调用子类Cat的重写函数eat,发现是可以调用的,然后写一个函数,形参是Animal类型,然后传进去Animal类型和Cat类型的匿名对象,也就是给mycat(Animal类型的引用)赋值为两个对象,发现父类被重写函数和子类重写函数都可以调用,不过如果要使用mycat对象去调用特有成员函数miao会报错,把它由Animal类型转化为Cat类型就可以调用了
其次是向下转型
一个已经向上转型的子类对象,将父类引用转换为子类引用,也就是我们的猫猫类演示程序的最后一步强转,使编译类型与运行类型匹配
要求:
1.只能转换引用,不能转换对象
2.父类引用必须指向将要转换的子类对象
3.转型后可以调用子类的所有成员(现在已经是一个正确的子类对象)
演示程序参考向上转型的最后一步
优点:可以调用子类的特有函数了
缺点:运行容易出错
instanceof关键字
为了防止强转出错,使用该关键字可以判断被转换对象是否为目标类类型或者目标类的子类的类型,如果为true则可以被安全转换
格式:对象 instanceof 类名称
class Animal
{
void eat()
{
System.out.println("动物吃饭");
}
}
class Cat extends Animal
{
@Override
void eat()
{
System.out.println("猫猫吃猫条");
}
void miao()
{
System.out.println("猫猫喵喵叫");
}
}
class Dog
{
@Override
void eat()
{
System.out.println("狗狗吃骨头");
}
public class Test
{
public static void main(void *args)
{
Animal mycat=new Cat();
System.out.println(mycat instanceof Animal);
System.out.println(mycat instanceof Dog);
}
}
结果如下:
true
false
总结一下多态
优点:
1.代码灵活可拓展
2.代码复用性高
3.父子对象的灵活转换
4.代码拓展性
缺陷:
1.可读性差
2.运行效率下降
3.向上转型导致子类特有成员无法被访问
多态性应用:多态数组(类型为父类,元素实际类型为子类)
class Animal
{
void eat()
{
System.out.println("动物吃饭");
}
}
class Cat extends Animal
{
@Override
void eat()
{
System.out.println("猫猫吃猫条");
}
void miao()
{
System.out.println("猫猫喵喵叫");
}
}
class Dog extends Animal
{
@Override
void eat()
{
System.out.println("狗狗吃骨头");
}
}
public class Test
{
public static void main(void *args)
{
Animal[] animals = {new Animal(),new Dog(),new Cat()};
for(Animal a: animals){a.eat();};
}
}
结果如下
动物吃饭
狗狗吃骨头
猫猫吃猫条
避免在构造函数内使用重写方法(其他方法也尽量避免)
15.抽象类
抽象类就是被abstract修饰的类,类中被abstract修饰的方法叫做抽象方法,不需熬给出具体实现,类似于c++中的虚函数与抽象类,抽象类不能实例化对象
想要实例化对象,对其中的虚函数进行重写即可
// 抽象类 Animal
abstract class Animal {
// 抽象方法 eat
abstract void eat();
}
// Cat 类继承自抽象类 Animal
class Cat extends Animal {
// 提供 eat 方法的具体实现
@Override
void eat() {
System.out.println("猫猫吃猫条");
}
void miao() {
System.out.println("猫猫喵喵叫");
}
}
// Dog 类继承自抽象类 Animal
class Dog extends Animal {
// 提供 eat 方法的具体实现
@Override
void eat() {
System.out.println("狗狗吃骨头");
}
}
public class Test {
public static void main(String[] args) {
Animal[] animals = {new Cat(), new Dog()};
// 遍历数组,并调用每个元素的 eat 方法
for (Animal a : animals) {
a.eat();
}
// 额外验证:直接创建 Cat 和 Dog 对象并调用 eat 方法
Cat myCat = new Cat();
myCat.eat();
Dog myDog = new Dog();
myDog.eat();
}
}
结果如下
猫猫吃猫条
狗狗吃骨头
猫猫吃猫条
狗狗吃骨头
抽象类特性:
1.不能实例化对象,只能被继承
2.抽象方法不能private
3.不能被final和static修饰,因为要有子类继承
4.和c++一样,但凡一个抽象方法没有被重写,就还是抽象类
5.抽象类中不一定包含抽象方法,但包含抽象方法的一定是抽象类
6.抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
应用:被继承以实现多态,例子可以参考实例化抽象类
16.接口
如果说抽象类是从多个类中抽象出来的模板,那么接口就是比抽象类更进一步的抽象
接口的成员没有执行体,由全局变量与公共的抽象方法组成
1.接口是一种“引用数据类型”,完全抽象的,支持多继承,且一个接口可以继承多个接口,只有常量+抽象方法(类似于虚基类)
2.所有的元素都是public修饰的,抽象方法的public abstract可以省略,常量的public static final可以省略,方法不能有方法体
定义接口
public interface Animal
{
int a=10;
public abstract void eat();
void sound();
}
创建接口时,一般用I开头,形容词性的单词
接口不能直接使用,必须用一个实现类来实现接口,以及接口中的所有抽象方法
现在实现一下Animal接口
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫猫吃猫条");
}
@Override
public void sound() {
System.out.println("猫猫喵喵叫");
}
继承是extend,实现接口是implements
接口的特性:
1.接口是引用类型,无法直接new接口的对象
2.如果非要在abstruct前面加修饰符,则只能加public
3.接口中的方法不能在接口类中实现,类似于纯虚函数
4.重写接口方法时,必须加public,其他的或者省略都不行(jk8也可以用default)
5.接口中可以有变量,但是会被直接认为是常量
6.接口支持多继承,且每个interface都会生成一个class后缀名的文件
interface a{
}
interface b extends a{
}
interface c extends a,b{
}
7.接口中不能有静态代码块和构造方法
8.接口编译完成后字节码文件的后缀和类一样都是.class
9.还是那句话,一个类如果没有重写接口中的所有成员函数,则该类依旧是抽象类
接口的应用:实现多态
public interface Animal {
void eat();
void sound();
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫猫吃猫条");
}
@Override
public void sound() {
System.out.println("猫猫喵喵叫");
}
}
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("狗狗吃骨头");
}
@Override
public void sound() {
System.out.println("狗狗汪汪叫");
}
}
public class Test {
public static void main(String[] args) {
Animal[] animals = {new Cat(), new Dog()};
for (Animal animal : animals) {
animal.eat();
animal.sound();
}
}
}
结果
猫猫吃猫条
猫猫喵喵叫
狗狗吃骨头
狗狗汪汪叫
17.比较接口(java版的运算符重载)
java不支持运算符重载,但我就要比较两个类怎么办,调用Compareable接口并重写compareTo方法,假设student类有年龄和名字两项
class Student implements Comparable
{
public String name;
public int score;
public Student(String a,int b)
{
this.name=a;
this.score=b;
}
public String toString()
{
return this.name+this.score;
}
@Overrride
public int compareTo(Object o)
{
Student c=(Student)o;
return this.score-c.score;
}
}
这样实现没有问题,但是还有一种方法,就是使用java.lang包中的comparator接口,这个接口是专门比较类的,可以参考以下的实现
// 按照年龄排序
class Agecompare implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age- o2.age;
}
}
// 按照名字排序
class Namecompare implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
// 使用方法
Arrays.sort(student,new Agecompare());
Arrays.sort(student,new Namecompare());
18.异常
异常在java里除了常见的编译错误,还包括用户自己规定的情况,一般我们会选择自定义异常
try -catch -finally语句
try块:负责捕获异常
catch:在try捕获异常后,程序控制权转移到catch块上的异常处理程序,处理完继续运行,处理的内容通常是给出警告,提示,打印错误,修正参数,有多个catch块的话,必须按照先catch子类后catch父类的顺序,因为会自上而下就近处理异常
finally:最终执行的代码,用于关闭和释放资源
注意,try不能单独存在,必须与catch与finally语句同时存在
格式
try{
//一些会抛出的异常
}catch(Exception e){
//第一个catch
//处理该异常的代码块
}catch(Exception e){
//第二个catch,可以有多个catch
//处理该异常的代码块
}finally{
//最终要执行的代码
}
使用猫猫类来演示一下
class Cat
{
String name;
int age;
Cat(String name,int age)
{
this.name=name;
this.age=age;
}
void error()
{
try{
if(age==18)
{
System.out.println("刚满18岁");
}
else
{
System.out.println(name+":"+age);
}
}
catch(Exception e)
{
System.error.println("发生异常");
}
finally
{
System.out.println("异常检查完成");
}
}
}
public class Main {
public static void main(String[] args) {
Cat myCat = new Cat("小黑", 18);
myCat.error();
Cat anotherCat = new Cat("小白", 5);
anotherCat.error();
}
}
结果如下:
刚满18岁
异常检查完成
小白:5
异常检查完成
异常逻辑:程序中增加一个异常处理函数,使用try-catch-finally语句时,应该注意以下几点
1.形参:只有catch有
2.打印语句:当发生异常时,打印到标准输出是不合适的,应该打印到标准错误(观察也是标准错误)
3.无论有无异常,finally语句都会强制执行
4.catch对一组类的检查顺序:先子类再父类
5.当一段代码爆出异常后,是否会导致程序崩溃?不会,和多进程一样,单个进程报错只会终止该进程代码并处理异常,其他代码照样运行
6.try抛出多个异常,由多个catch块分别执行(try由返回类,修改全局变量等传出信息,由catch块接收分别执行)
7.比finally优先级低的只有return,不建议在finally中return,会导致程序提前退出
8.如果try-catch-finally语句要进行返回,就在finally语句后面返回
9.e.printStackTrace()可以输出异常信息,return -1也是可以抛出异常的(和linux习惯抛出非0整数表示错误类型不同,java喜欢抛出-1)
10. 如果方法中try,catch,finally中没有返回语句,则会调用这三个语句块之外的return结果
11.finally 在try中的return之后 在返回主调函数之前执行
关键字throw和throws
throw:将产生的异常抛出,和return类似是一个动作,一般采用这样的格式
class Cat {
String name;
int age;
Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void error1() {
if (age == 18) {
throw new Exception("猫的年龄不能是18岁!");
}
System.out.println(name + ":" + age);
}
}
public class Main {
public static void main(String[] args) {
Cat myCat = new Cat("小黑", 18);
try {
myCat.error1();
} catch (Exception e) {
System.err.println("捕获到异常: " + e.getMessage());
}
Cat anotherCat = new Cat("小白", 5);
anotherCat.error1();
}
}
throws声明抛出异常的类型
throws的格式:
1 public void 方法名(参数列表)
2 throws 异常列表{
3 //调用会抛出异常的方法或者:
4 throw new Exception();
5 }
改造一下我们的猫猫类即可
class Cat {
String name;
int age;
Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void error1()throws Exception { //就这里
if (age == 18) {
throw new Exception("猫的年龄不能是18岁!");
}
System.out.println(name + ":" + age);
}
}
public class Main {
public static void main(String[] args) {
Cat myCat = new Cat("小黑", 18);
try {
myCat.error1();
} catch (Exception e) {
System.err.println("捕获到异常: " + e.getMessage());
}
Cat anotherCat = new Cat("小白", 5);
anotherCat.error1();
}
}
注意:
1.throws的异常列表可以是抛出一条异常,也可以是抛出多条异常,每个类型的异常中间用逗号隔开
2.方法体中调用会抛出异常的方法或者是先抛出一个异常:用throw new Exception() throw写在方法体里,表示“抛出异常”这个动作。
3.如果某个方法调用了抛出异常的方法,那么必须添加try catch语句去尝试捕获这种异常, 或者添加声明,将异常抛出给更上一层的调用者进行处理
自定义异常(就是throws返回对象类型的原因),写一个Exception标准异常的子类类型,使用try进行更符合需要的返回,采用以下格式
class Exception1 extends Exception {
public Exception1(String message) {
super(message);
}
}
这样关于捕捉和处理多个异常(自定义异常的成员不同)来进行更为精细化的处理
19.流
流的分类:
1.流动方向上,分为输入流和输出流(类似stdin,stdout)
2.读取类型上,分为字节流与字符流
3.发生的源头上,分为节点流和过滤流
首先是输入输出流,我们只做简单程序(按模板记忆)
例子
从键盘输入字符串并统计数字的数量
import java.io.*
public class Main
{
public static void sum(char[]arr)
{
int n=0;
for(int i=0;i<arr.length;i++)
{
if(arr[i]>='0'&&arr[i]<='9')
{
n++;
}
System.out.println(n);
}
}
public static void main(String argv[])
{
char arr[]=new char[100];
int count=System.in.read(arr);
for(int i=0;i<count;i++)
{System.out.println(arr[i]);
}
sum(arr);
}
}
主要代码解析,首先arr是我们存储结果的数组,首先new初始化,然后调用System.in.read来从屏幕输入数据,并且会返回字符的数量,我们可以利用此数量打印字符串(注意,字符串的length是属性,不用加括号),然后对字符串进行判断操作并打印
字节流,就是以字节为单位读取数据,java提供了两个抽象类InputStream和OutputStream,和in和out一样都属于顶级父类,所有字节输入输出流都继承于它
对应读取文件和写入文件的操作
读取文件
import java.io.*
public class Main
{
public static void main(String []args)throws IOException
{
try
{
FileInputStream rf=new FileInputStream("open.java");
int n=512,c=0;
byte buffer[]=new byte[n];
while((c=rf.read(buffer,0,n))!=-1)
{
System.out.print(new String(buffer,0,c));
}
}
catch(IOException ioe)
{
System.out.println(ioe);
}
catch(Exception e)
{
System.out.println(e);
}
}
}
这是用FileInputStream读文件,很可能考试会考,重点在于抛出的异常类型是IOException,然后创建512个字节的缓冲区bute,然后在while循环中一直读到末尾,并且不断将缓冲区中的内容转换为字符串并打印,如果发现异常ioe就打印信息
第二个例子
public static void main(String[] args) throws Exception {
InputStream input = null;
try {
// 创建一个文件字节输入流
FileInputStream in = new FileInputStream("src/IO/test.txt");
int b = 0; // 定义 int 类型的变量 b,用于 记住每次读取的 1 字节
while (true) {
b = in.read(); // 变量 b 记住读取的每一字节
if (b == -1) { // 如果读取的字节 位 -1,则跳出循环
break;
}
System.out.print(b + " "); // 否则输出b
}
} finally {
if (input != null) {
input.close();
}
}
}
这次给出的是文件路径,使用变量b记录每一字节,然后输出文件内容
写入文件
public static void main(String[] args) throws Exception {
OutputStream out = new FileOutputStream("src/IO/example.txt");
String str = "Island1314";
byte[] b = str.getBytes();
for(int i = 0; i < b.length; i++){
out.write(b[i]);
}
out.close();
}
和输入差不多,记忆即可
接下来是字符流(字节流+编码表)
字符流读文件
public static void main(String[] args) throws Exception {
// 创建一个 FileReader 对象,用来读取文件字符
FileReader reader = new FileReader("src/IO/test.txt");
int ch; // 用于记录读取的字符
while((ch = reader.read()) != -1){ // 循环判断是否读到文件末尾
System.out.print((char) ch); // 不是文件末尾就打印字符
}
reader.close(); // 关闭字符输入流,释放资源
}
// 输出
itcast
字符流写文件
public static void main(String[] args) throws Exception {
// 创建一个 FileWriter 对象,用于向文件写入数据
FileWriter writer = new FileWriter("src/IO/example.txt");
String str = "222";
writer.write(str); // 将字符数据写入到文本文件中
writer.write("\r\n"); //输出换行
writer.close();
}
与字节流大同小异,记忆即可
过滤流:BufferedWriter和BufferedReader
public static void main(String[] args) throws IOException {
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("src/IO/test.txt"));
//写数据
for (int i = 0; i < 10; i++) {
bw.write("hello" + i);
//bw.write("\r\n");
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("src/IO/test.txt"));
String line;
while ((line=br.readLine())!=null) {
System.out.println(line);
}
br.close();
}
序列化:
对象序列化与反序列化(介质存储和网络传输)