Java语言基础技术相关整理

Java基础的书籍视频基本也早都看完了,本来以为自己在java基础上应该没什么问题了。但是今天看开发视频的时候突然发现有关抽象的定义有些模糊不清了。因此写个博客整理一下自己的所学与所掌握的知识。

事先声明:本博文是一个初学者的学习笔记,因此欢迎各位大佬指出错误。

一、java 的基本知识

1:什么是面向对象?
关于这个问题,在我刚开始学C++的时候问群里的大佬,他们告诉我是万物皆对象。
后来学java的时候,有着这样的话:抽象、继承、多态、封装是Java作为面向对象的重要特征。因为自己从c语言入门开始学习,我很难明白这些话。
什么叫万物皆对象?抽象、继承、多态、封装。这些不就是一些比较特殊的语法吗,为什么可以作为面向对象的重要特征呢?
前段时间看了些视频,对这个问题感觉略有些理解了,举个简单的例子——递归。
在学习c语言的时候,我理解递归的方法是,从形参的传入开始,画参数传递图,通过理解他们的每一层的调用来理解什么是递归。
但当我理解了面向对象后这个问题就变的容易了。比如说常见的斐波那契问题

int fun(int n)
{
if(n==0)
return 0;
if(n==1)
return 1;
//因为当前数字等于前一个数字加上前一个数字的前面的一个数字
//所以我就先直接假设我的fun函数能正确的运行并返回当前的数字
//因此返回值可以这样写
return fun(n-1)+f(n-2);
//之后我们再返回前面处理特殊值
}

这样,通过面向的对象的思想我不在需要去理会递归中的层层调用,就可以很轻松的理解并运行这个递归函数。
因此暂时先做自己的理解:面向对象的思想就是在合适的程度上“不求甚解”

二、封装、继承、多态、抽象、接口、反射、I/O流

既然提到了,抽象、封装、继承、多态等我就来记录下Java是怎么实现这些这些面向对象的特征的,再顺便复习一下泛型、接口这些java中重要的知识点。
2.1 封装
封装的实现对我来说是java中最简单的部分,因为c++中也有这个东西。那就是类。
java用类实现封装的功能。将一个对象分为“函数”和“参数”两个部分。函数通常表示对象的某种行为,参数通常用来表示对象的某些特征和数据。
不管是函数还是字段,在java中都有三种修饰方式。
private(私有类型:为了保持封装,字段通常都为私有类型)
public(公有类型)
protected(受保护的类型:可以被子孙,朋友等继承类调用,也可被同一个包的其他类调用)
当你不适用修饰符的时候java默认是使用public来修饰的。
关于修饰符更详细的记录可以查看这位博主的博客[https://blog.csdn.net/autumn20080101/article/details/8101452]

通常情况下这三种权限是绝对的,但是往往事有例外,因此就算是私有类也有办法可以被调用,那就是——反射

那么类在内存中存在的方式是什么呢?一个对象是单独分配一个类空间吗?还是说别的什么样子呢?
事实上多个类对象在内存中的存在方式是“数据单独存储,方法共同拥有”。如下图
在这里插入图片描述
那么这些方法是怎么判断是哪个对象呢?有没有可能m的值被变成2,n的反而为1呢?java中使用了this指针来解决这个问题。我图中所用的this的意思就是当前对象的意思。
比如当使用m时这个this.a等同于m.a,同理当使用对象n时this.a等同于n.a。

2.2 反射
反射是java内比较的特殊的一个知识点。实际上反射不仅仅可以访问私有类,私有对象,私有方法等,它还可以在编译之后调用对象。
详情可查看我的另一篇博文:https://blog.csdn.net/qq_34034111/article/details/88176020
这里不再复述。
2.3 继承
继承的主要作用是支持代码服用
java中使用extends关键字实现继承。值得注意的是java中仅仅支持单继承。
在java中子类拥有父类的所有的变量和函数。但值得注意的是,子类不一定能使用父类所有的变量和函数。例如子类无法使用父类中的私有变量和私有函数。但是子类在内存中实际上是拥有这些父类私有的东西的,只不过是无法使用而已。
实际上如果子类重载类父类的方法时,当你用子类对象调用这个方法它会动态调用子类的方法而不是父类的,但是这并不代表父类的方法就被覆盖了,它依旧存在只不过你无法普通调用它们而已,但是你可以使用super关键字去调用父类被重载的方法。

关于继承几个需要注意的点:
1:用父类可以接收子类对象,但这时只能调用父类已有的方法。但是如果父类方法被子类重载了,这个时候,虽然是用父类对象调用的方法,但是它依旧会动态的调用子类重载后的方法。

2:子类对象可以强转成父类对象,但父类对象仅能在有限的条件下转换为子类对象。这个条件就是,该父类对象是由子类对象转换过来的。
通过这一条我们也可以看出,当子类对象转换为父类对象的时候,子类对象特有的功能并没有被删除,它只是被隐藏了起来。

3:子类对象被创建时,父类的构造函数也会被引用(但是注意,这个时候并没有创建父类对象,父类构造函数只是初始化了父类的变量。这点很奇怪,你可查看这里了解具体为什么[https://www.zhihu.com/question/51920553])。
具体顺序为父类静态代码块→子类静态代码块→父类动态代码块→父类构造函数→子类动态代码块→子类构造函数。(此默认为第一个子类创建对象)。当创建第二个子类对象是顺序略有不同,这很容易理解。
可以查看这位博主[https://blog.csdn.net/baidu_38760069/article/details/79915652]的具体解释,也可以自己思考。

继承时内存的分配问题
通过对内存分配的理解我们可以对继承时的一些问题了然于心更有助于对继承的理解。
该知识点的梳理主要取材于这位博主的博客↓
https://blog.csdn.net/qq_34188112/article/details/54969801
在java中,内存的大致可以分为堆、栈、方法区这三种。
关于堆、栈的详细解释可以查看这位博主的博客↓
[https://blog.csdn.net/u012031380/article/details/54981472]
有关方法区与更详细的堆栈的内容可以查看这位博主的博客↓
[https://blog.csdn.net/zhangqiluGrubby/article/details/59110906]
这里我只是简单的说一下:
1:基础类型的变量和引用储存在栈里面。如:int a=3;String str=“abc”;
2:上述例子中String作为引用类型它的引用 str 会被存入栈里,但是“abc”并不会被存入栈中,“abc”会被存入常量池(该定义见ps1)中。
3:但如果是使用String str=new String(“abc”)这样创建的话,new会在编译之后运行。这时new会先去常量池查看new String(“abc”)这个对象是不是存在,若存在则从常量池拷贝到堆,若不存在则先将对象new String(“abc”)加入常量池,再从常量池拷贝到堆里;
4:方法区,是一块独立的区域。方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息等);
ps1:常量池与栈共享数据,用来存储编译期间生成的字面量和符号引用、静态变量、常量以及编译器编译后的代码等。

也就是说,栈内存储的是基础类型的变量与引用。堆内用来存放对象。方法区内用来存储类的基本信息

//这个是父类
public class text1 {
int a=0; 
public void addf()
{
}
}
//这个是子类
public class text2 extends text1{
int a=1;
public void add()
{
}
}
//这个是测试类
public class text3 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
     text1 demo1=new text2();
     text2 demo2=new text2();
	}
}

我们来分析一下这段代码在内存中的情况
在这里插入图片描述
这样demo1就成功指向了一个text2对象(但值得注意的是,如果像上述将堆理解为两个圆的话,demo1指向的是text1这个类的下面的那个text2部分。)。
demo2的流程和demo1相似,只不过demo2指向的是整个text2这个类而已。

最后,提到继承就不得不说下,一个在c++书上往往和它连在一起说的一个知识点友元函数
但是在java中友元函数却被废除了,因为它破坏了面向对象的关系,使封装变得不那么严密。取而代之的是java引入了包的概念,java默认支持包内友好访问,同一个包内的类可以默认访问没有被priavte修饰符修饰的其他类的数据和方法。
想要在java中实现友元的类似效果要么使用反射,要么使用接口创建内部类来实现友元函数。

2.4 接口与抽象
接口与抽象我往往容易弄混,因此把它们放在一起说可以拿来形成对比。
这两个知识点的主要由这两位博主的文章中提炼而来:
该博主对抽象与接口语义上的不同有重点解释:[https://blog.csdn.net/qq_31655965/article/details/54972723]
该博主对抽象与接口语法上的不同有详细解释:[https://blog.csdn.net/qq_32876359/article/details/78970776]
关键字
接口使用interface来作为关键字,抽象使用abstract来作为关键字。

抽象有着抽象类与抽象方法两种情况,但interface只能用来修饰类。

  •  抽象可以使用的情况
    public abstract class A{}
    abstract void A(){}
    ps1:一个类如果有抽象方法,那么这个类会被默认为抽象类。
    ps2:有抽象方法的类一定是抽象类,但抽象类内不一定有抽象方法。
    ps3:抽象方法的修饰词可以为public和protected。
    
  • 接口可以使用的情况      
    public interface class A{}
    ps1:接口内的方法类型只能是public abstract 类型的方法。
    ps2:接口内的变量只能为 public static final类型的常量。
    

在类的实现上,我们**继承(extends)**抽象类,**实现(implements)**接口。因此我们只能继承一个抽象类但可以实现多个接口

抽象类和接口类的相同点:
1:抽象类和接口类都不能创建对象;
2:一般类继承了抽象类或实现了接口类的时候,都需要实现所有的抽象方法。
3:java8以后两者内都可以有静态代码块。(这样子类可以选择实现该静态方法,也可以选择不实现该方法)如图↓
在这里插入图片描述
3:如果继承或实现了抽象或接口,那么子类必须全部实现两者的abstract方法(放在接口上就是必须实现所有方法)否则子类也将成为一个抽象类。
抽象类和接口类的不同点(语法上):

抽象类接口
抽象类可以有普通方法可抽象方法,极端情况下可以全部都是普通方法。接口内的方法只能为抽象方法,不能为普通方法
抽象类内可以有一般变量接口内的变量类型只能为public static final类型的常量
抽象类内可以实现普通方法,也可以实现静态方法(java8前)接口内只能申明abstract方法,而不能实现(java8前)
只能继承一个抽象类可以实现多个接口

我自己理解的语义的层次上的抽象与接口:抽象是对事物的抽象,而接口是对行为的抽象。
通过语法我们可以看出接口实际上只能声明函数,而在类中函数往往代表着对“行为”的抽象。即能做什么?例如我们创建一个教学接口,在教学中我们可以说话,可以翻书,但往往不会有某些具体的属性,在特殊的情况下可能会有一些特殊的属性如“知识”,因此可以声明一些static final类型的属性。而每个老师都可以教学但他们说的话往往也是不同的,这往往是由具体哪一科(实现了教学接口的类)来决定。

而抽象类除了不能创建对象的限制外,可以说与普通函数没什么区别。抽象是对事物的一种概括,它可以有具体的属性,也可以有具体的行为,但是不可能有具体的对象。如:Anmial类作为一个抽象类,它可以有某些属性如寿命等,也可以有某些行为如进食等,但是你无法找到一个名为动物的生物。

2.5:多态
在java中多态主要是依靠方法的重写与方法的重载重载来实现的。
方法的重载:在同一个类中方法名相同但参数个数或类型不同。(但是注意java中返回值不是判断重载的因素)
方法的重写:在父类与子类之间发生。子类中对父类中的某个方法进行了重写,该方法与父类中方法的名字和参数类型都相同。(但注意这个时候父类中的方法依旧被子类继承了,只不过无法调用而已。)

提到了多态就有一个无法被忽略的问题要去理解:静态绑定动态绑定
在c++中这个知识点是必须掌握的,因为它使用虚函数来实现动态绑定的功能。
在java中你可以只知道这两个名词而不去理解其内部机制,因为java自动实现了动态绑定的效果。
但是理解这两个知识点有助于对重载与重写的深度理解,因此我来梳理一下这两个知识点。
这个知识点的理解主要来自于这位博主的文章↓
[https://blog.csdn.net/lingzhm/article/details/44116091]

静态绑定动态绑定
发生阶段编译阶段运行阶段
一般被绑定的内容方法的参数、被final、static、private修饰的方法、构造函数除前面的静态绑定的内容之外java中其余的全部为动态绑定

有关动态绑定的的内存分配可以查看2.3中的继承时内存的分配问题

2.6 I/O流
有关I/O流可以查看我的这篇文章↓
https://blog.csdn.net/qq_34034111/article/details/88168264

三:泛型、多线程

虽然泛型和多线程也是java中重要的基础知识,但是要详情介绍两者的机制,要牵扯到更多高级的知识,因为是java基础方面的文章,在这里追求会用就行。
如果想要要学习更加深入的泛型可以查看这位博主的博客↓
https://blog.csdn.net/qq_39521554/article/details/79981645
3.1 泛型
泛型的目的:泛型的目的是将错误控制在编译之前

泛型的特性:泛型只会在编译过程中有效,编译之后就会擦除泛型,因为在编译之后泛型已经没有用了。并在对象进入和离开的边界处添加类型转换。

泛型的使用:泛型类,泛型方法,泛型接口,容器
泛型中我们常用的容器一般包括 List、Map、Set这些,可以查看我的另一篇博文[https://blog.csdn.net/qq_34034111/article/details/88136322 ]这里不再复述。
其语法分别为:

  •  1:泛型类 创建
       class 类名(例如为:text) <任意字符(相当于一个标识:例如为T)>{
       private <T> s;//当你给类通过泛型输入了一个类型后这个s的类型就和T相同
      }
      2:泛型类 引用
      text <任何类>text1=new text<这里可以填和前面一样的类,也可以直接空着>();
    
  • 2:泛型接口 创建
         public interface text <T>{
         public T nice();
      	}
      	泛型接口 实现
      	class text1 <T> implement text<T>{//若实现时尚未确定泛型类型则这样写
      	public T nice()
      	{
      	return null;
      	}
      	}
      	
      	class text1 implement text<String>{//若已经确定传入类型,则直接这样写
      	//这样一来上面的实现中为什么要再text1中加<T>就很明显了,这样才可以在创建text1对象时确定到底应该传入什么类型
      	......
      	}
    
  •      3:泛型方法 创建(假设两个方法都存在于一个名为Method的普通类中)
          public <T> T textMethod ()  {}无参方法
          public <T> T textMethod (text<T> m)  {}传入参数为泛型类
          泛型方法 引用
           Method m=new Method();
           m.<填入某个类>textMethod();
    

泛型的通配符

  •   通配符的语法
      如果开始时是
      public void textMethod(text<Number> m)
      只需要改为↓
        public void textMethod(text<?> m)
        注意:不需要修改声明中的形参。
    

通配符其实就是用?代替之前的类型实参。?是类型实参,也就是说它是实际存在的类型,你可以把它当成Object来理解。
它的作用一般是:例如当你想往一个泛型类里分别传入Number和Integer,因为从逻辑上这两个类型是一样的,Integer实际上是Number的一个子类。你就可以用?代替原来的类型形参,这个时候你就可以使用Number和Integer两者所共有的那些属性和方法。

泛型的上界与下界
对该知识点更详细的理解可以查看一下博文↓
https://blog.csdn.net/yabay2208/article/details/77967537
上界只能 get,不能 set
下界只能 set,不能 get

  • 泛型的上界
    public text<? extends Number> getMethod(text<?extends Number> a)
    {
    return a;
    //因为传入的是Number的子类,所有都可以返回为Number的泛型。
    }
       public void setMethod(text<?extends Number> a)
       {
       //因为Number有许多子类我们无法确定具体是什么类型的泛型,所以无法赋值。
      }
    泛型的声明应改为
    class text<T extends Number>
    这句话的意思就是说传入一个上界为Number的text的泛型类。
    直白的说就是,text的泛型可以是所有Number的子类。
    
  • 泛型的下界
    public void setMethod(text<?super Integer> m)
    {
    text <?super Integer> t=new text<Number>();
    t=m;
    //Integer一定为所传入泛型类的子类,一次所以可以直接将该泛型赋值给t;
    }
    public text<? super Integer> getMethod()
    {
    //Integer一定为所传入泛型类的子类,但是最上方可追溯至Object类型,你根本无法判读传入的是哪一种类型的泛型,怎么返回?
    }
    泛型的声明应改为
    class text<T super Integer>
    
    这句话的意思是说传入一个类型,这个类型一定要是Integer的父类。

从泛型的特性里我们看出来,泛型在运行时是不参与的。因此泛型的作用其实就是在编译前制止可能出现的错误,然后在编译时彻底确认下来每个方法、类、参数所使用的具体类型。
那么我们在使用泛型的时候,只要注意在符合规定语法的前提下,你创建的各项数据的类型都确定下来,在泛型的使用的时候就不会出现什么大的问题。
3.2 多线程
什么是多线程?
什么是线程?
什么是进程?
进程:直白的说就是我们平时所用的每一个软件就是一个进程。
线程:一个进程内可以有一个线程,也可以有多个线程。
多线程:多个线程就是多线程,可以加快进程的速度。
为什么多线程可以加快进程的速度?
从操作系统上我们可以知道,cpu会分为许多时间片,每一个时间片都会随机分配给某个线程使用,多个线程分配的几率更高。感觉上就相当于多个线程能同时多处理多个业务。

java中的线程
略深学习线程的话可以查看这位博主的博客↓
https://blog.csdn.net/lijizhi19950123/article/details/78024612

class MyThread implement Runnable{//实现Runnable
public run(){//重写run方法
。。。。
里面加你要实现的代码
线程会从run函数进入,相当于进程中的main函数。
}
}
public class RunnableDemo{
    public static void main(String args[]){
    //创建线程对象
    MyThread mt1=new MyThread();
     MyThread mt2=new MyThread();
     //实例化线程对象
     Thread t1=new Thread(mt1);
      Thread t2=new Thread(mt2);
      //启动线程
      t1.start();
      t2.start();
	}

这样一个两个线程的进程就完成了。
我们也可以通过继承Thread来实现创建多线程,如果使用Thread的话我们甚至不需要实例化线程对象,在我们创建线程对象的时候java就会帮我们实例化该对象。
但是我依旧推荐使用Runnable,因为java只支持单继承,如果继承了Thread就无法再继承其他类。

java中多个线程的运行没有规律,基本可以说是会随机在你创建的线程中随机运行直至结束。
但是如果使用同一个资源时,就可能会出现冲突,比如说卖火车票A卖了这张票,B也卖了这张票。因此我们可以使用锁来让线程按规律运行。
什么是锁?
我们可以想象这样一个场景:有一个厕所,张三进入之后插上了门,厕所外面牌子变成红色,这时李四走了过来他想进入厕所就只能在外边等着直到张三出来…
这个插上门的过程就是上了一个锁。
在java中锁的实现使用synchronized()来对代码块实现加锁。
例如:sychronized(张三){上厕所}

这样,java的基础知识就基本整理完成。因为有些地方是本人自己的理解,很可能不是那么正确,如果有大佬看出来希望能指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值