Java の基本入門





Java について知っておくべきことは何ですか?この記事を読めば詳しく理解できるので、ぜひ集めてください!

Javaの基本

オブジェクト指向の3大特徴  


機能:カプセル化、継承、ポリモーフィズム。
カプセル化:物事をオブジェクトに抽象化し、オブジェクトのプロパティをプライベート化し、外部からアクセスできるメソッドを提供します。
継承:サブクラスは、単一の継承と複数の実装を使用して、新しいデータ フィールドまたは関数を拡張し、親クラスの属性と関数を再利用します。
ポリモーフィズム:継承 (同じメソッドを書き換える複数のサブクラス) またはインターフェイス (インターフェイスの実装とインターフェイスのオーバーライド) を通じて。

  • Java と C++ の違い

相違点: C++ は多重継承をサポートしており、ポインターの概念があり、プログラマーは独自にメモリーを管理します。Java は単一継承を持ち、インターフェースを使用して多重継承を実装できます。Java はメモリーに直接アクセスするためのポインターを提供しません。 JVM の自動メモリ管理メカニズムでは、プログラマが未使用のメモリを手動で解放する必要はありません。

  • ポリモーフィック実装の原則


ポリモーフィズムの基礎となる実装は動的バインディングであり、実行時にメソッド呼び出しとメソッド実装を関連付けます。

静的バインディングと動的バインディング:

1 つはコンパイル時に決定され、メソッドのオーバーロードなどの静的ディスパッチと呼ばれます。

1 つは実行時に決定され、メソッドのオーバーライド (書き換え) やインターフェイスの実装など、動的ディスパッチと呼ばれます。

ポリモーフィックな実装

現在のメソッド呼び出しのスタック フレーム (ローカル変数テーブル、操作スタック、動的接続、リターン アドレス) は仮想マシン スタックに保存されます。ポリモーフィズムの実装プロセスは、メソッド呼び出しの動的ディスパッチのプロセスです。サブクラスが親クラスのメソッドをオーバーライドする場合、ポリモーフィック呼び出しでは、動的バインディング プロセスが最初に実際の型がサブクラスであると判断します。まずサブクラスメソッドを検索します。このプロセスがメソッド カバレッジの本質です。


  • 静的キーワードと最終キーワード


static:プロパティとメソッドは変更可能
静的に変更された属性:
すべてのオブジェクトで共有されるクラスレベルの属性は、クラスがロードされるときにロードされます (一度だけロードされます)。オブジェクトが作成される前に、クラス名を使用して直接呼び出すことができます。
静的変更方法:
クラスがロードされるとロードされます。クラス名を使用して直接呼び出すことができます。静的メソッドでは静的メンバーのみを呼び出すことができ、これは使用できません。
Final:このキーワードは主に、変数、メソッド、クラスの 3 つの場所で使用されます。
最終的に変更された変数:
基本データ型の変数の場合、初期化後に値を変更することはできません。
参照型変数の場合、初期化後に別のオブジェクトを指すことはできません。
最終的な修正方法:
メソッドをロックして、継承クラスがその意味を変更 (オーバーライド) できないようにします。クラス内のすべてのプライベート メソッドが暗黙的に Final として指定されます。
最終的に変更されたクラス:
Final がクラスを変更する場合、このクラスは継承できないことを示します。最終クラスのすべてのメンバー メソッドは、暗黙的に最終メソッドとして指定されます。
クラスは継承できません。final キーワードに加えて、コンストラクターもプライベート化できます。 (内部クラスは無効です)

  • 抽象クラスとインターフェイス

抽象クラス:抽象メソッドを含むクラス、つまり抽象クラスで変更されたクラスは継承のみが可能なため、最終的な変更は使用できず、抽象クラスはインスタンス化できません。
インターフェイス:インターフェイスは、抽象型および抽象メソッドのコレクションです。インターフェイスで定義されたメソッドは、デフォルトでパブリック抽象によって変更された抽象メソッドをサポートします。
同じ点:
  1. 抽象クラスもインターフェイスもインスタンス化できません。
  2. 抽象クラスとインターフェイスの両方で抽象メソッドを定義でき、サブクラス/実装クラスはこれらの抽象メソッドをオーバーライドする必要があります。
違い:
  1. 抽象クラスにはコンストラクターがありますが、インターフェイスにはコンストラクターがありません。
  2. 抽象クラスには通常のメソッドを含めることができ、インターフェイスはパブリック抽象 (Java 8 以降で使用可能) を使用して抽象メソッドのみを変更できます。
  3. 抽象クラスは 1 回のみ継承できますが、インターフェイスは複数回継承できます。
  4. 抽象クラスはさまざまなタイプのメンバー変数を定義でき、インターフェイスは public static Final によって変更される静的定数のみにすることができます。
抽象クラスの使用シナリオ:
サブクラスが共通の動作を持つように制約するだけでなく (ただし、その実装方法は問わない)、デフォルトのメソッドとインスタンス変数も必要になります。
インターフェイス アプリケーションのシナリオ:
複数の実装クラスが均一の動作を持つように制約しますが、各実装クラスがどのように実装されるかは考慮されません。実装クラス内のさまざまな関数間に接続がない場合があります。
  • ジェネリックとジェネリック消去

参考:https://blog.csdn.net/baoyinwang/article/details/107341997
ジェネリック:
ジェネリックの本質はパラメータ化された型です。このパラメータ タイプは、それぞれジェネリック クラス、ジェネリック インターフェイス、ジェネリック メソッドと呼ばれるクラス、インターフェイス、およびメソッドの作成に使用できます。
一般的な消去:
Java のジェネリックスは疑似ジェネリックスです。ジェネリックスを使用すると、型パラメータが追加されます。このプロセスは、コンパイラが生成されたバイトコードをコンパイルするときに削除されます。
List などの型はコンパイル後 List になります。 JVM が認識できるのはリストだけであり、ジェネリックによって付加された型情報は JVM には見えません。
他のタイプの要素は、リフレクションを通じて追加できます。

  • リフレクションの原理と使用シナリオ

Java リフレクション:
これは、実行状態では、どのクラスでも、このクラスのすべてのプロパティとメソッドを知ることができ、そのメソッドのいずれかを呼び出すことができることを意味します。
反射原理:
リフレクションは、まず Java のリフレクション クラスのバイトコードを取得し、次にバイトコード内のメソッド、変数、コンストラクターなどを、対応するメソッド、ファイル、コンストラクター、およびその他のクラスにマップします。
クラスのインスタンスを取得する方法:
  
  
  
  
  
1.类名.class(就是一份字节码)2.Class.forName(String className);根据一个类的全限定名来构建Class对象3.每一个对象多有getClass()方法:obj.getClass();返回对象的真实类型
使用するシーン:
  1. 共通のフレームワークを開発する- リフレクションの最も重要な用途は、さまざまな共通のフレームワークを開発することです。多くのフレームワーク (Spring など) が構成されています (XML ファイルを介して JavaBeans やフィルターなどを構成するなど) フレームワークの汎用性を確保するには、さまざまなオブジェクトまたはクラスを動的にロードし、それに応じてさまざまなメソッドを呼び出す必要があります。実行時の構成ファイル。

  2. 動的プロキシ - アスペクト プログラミング (AOP) では、特定のメソッドをインターセプトする必要があります。通常、動的プロキシ メソッドが選択されます。このとき、それを実現するにはリフレクション技術が必要となります。

    JDK: Spring のデフォルトの動的プロキシはインターフェースを実装する必要があります。

    CGLIB: asm フレームワークを介してバイト ストリームをシリアル化します。構成可能ですが、パフォーマンスは低下します。
  3. カスタム アノテーション- アノテーション自体はマークとしてのみ機能します。アノテーション マークに従ってアノテーション インタプリタを呼び出し、動作を実行するには、リフレクション メカニズムを使用する必要があります。


  • Java例外システム

Throwable は、Java 言語のすべてのエラーまたは例外のスーパー クラスです。次のレベルはエラーと例外に分かれています。

エラー:

Java ランタイム システムの内部エラーおよびリソース枯渇エラーを指します。アプリケーションはこのクラスのオブジェクトをスローしません。このようなエラーが発生した場合、ユーザーに通知するだけでなく、残りはプログラムを安全に終了することを試みます。

例外に含まれるもの:RuntimeException、CheckedException;

プログラミング エラーは、構文エラー、論理エラー、操作エラーの 3 つのカテゴリに分類できます。

構文エラー(コンパイル エラーとも呼ばれます) は、コンパイル プロセス中に発生するエラーで、コンパイラによって構文エラーがチェックされます。

論理エラーとは、プログラムの実行結果が期待と一致しないことを意味し、デバッグによってエラーの原因を突き止めて発見することができます。

実行時エラーはプログラムの異常終了を引き起こすエラーであり、実行時エラーは例外処理によって処理する必要があります。

RuntimeException:実行時例外。プログラムは論理的な観点からそのような例外を回避するように努める必要があります。

NullPointerException、ClassCastException など。

CheckedException:チェックされた例外。プログラムは trycatch を使用してキャッチし、処理します。

IOException、SQLException、NotFoundExceptionなど;


  数据结构



  • ArrayList和LinkedList


ArrayList:

底层基于数组实现,支持对元素进行快速随机访问,适合随机查找和遍历,不适合插入和删除。(提一句实际上)

默认初始大小为10,当数组容量不够时,会触发扩容机制(扩大到当前的1.5倍),需要将原来数组的数据复制到新的数组中;当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。

LinkedList:

底层基于双向链表实现,适合数据的动态插入和删除;
内部提供了List接口中没有定义的方法,用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。(比如jdk官方推荐使用基于linkedList的Deque进行堆栈操作)

ArrayList与LinkedList区别:

都是线程不安全的,ArrayList适用于查找的场景,LinkedList适用于增加、删除多的场景。

实现线程安全:

可以使用原生的Vector,或者是Collections.synchronizedList(List list)函数返回一个线程安全的ArrayList集合。

建议使用concurrent并发包下的CopyOnWriteArrayList的。

Vector:底层通过synchronize修饰保证线程安全,效率较差。

CopyOnWriteArrayList:写时加锁,使用了一种叫写时复制的方法;读操作是可以不用加锁的。


  • List遍历快速和安全失败


普通for循环遍历List删除指定元素

  
  
  
  
  
for(int i=0; i < list.size(); i++){   if(list.get(i) == 5)        list.remove(i);}


 迭代遍历,用list.remove(i)方法删除元素

  
  
  
  
  
Iterator<Integer> it = list.iterator();while(it.hasNext()){    Integer value = it.next();    if(value == 5){        list.remove(value);    }}


foreach遍历List删除元素

  
  
  
  
  
for(Integer i:list){    if(i==3) list.remove(i);}


fail—fast:快速失败

当异常产生时,直接抛出异常,程序终止。

fail-fast主要是体现在当我们在遍历集合元素的时候,经常会使用迭代器,但在迭代器遍历元素的过程中,如果集合的结构(modCount)被改变的话,就会抛出异常ConcurrentModificationException,防止继续遍历。这就是所谓的快速失败机制。

fail—safe:安全失败

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。

缺点:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。


  • 详细介绍HashMap


角度:数据结构+扩容情况+put查找的详细过程+哈希函数+容量为什么始终都是2^N,JDK1.7与1.8的区别。

参考:https://www.jianshu.com/p/9fe4cb316c05

数据结构:

HashMap在底层数据结构上采用了数组+链表+红黑树,通过散列映射来存储键值对数据。

扩容情况:

默认的负载因子是0.75,如果数组中已经存储的元素个数大于数组长度的75%,将会引发扩容操作。

【1】创建一个长度为原来数组长度两倍的新数组

【2】1.7采用Entry的重新hash运算,1.8采用高于运算。

put操作步骤: 


1、判断数组是否为空,为空进行初始化;
2、不为空,则计算 key的hash值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
3、查看table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;
4、存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据;
5、若不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;
6、若不是红黑树,创建普通Node加入链表中;判断链表长度是否大于8,大于则将链表转换为红黑树;
7、插入完成之后判断当前节点数是否大于阈值,若大于,则扩容为原数组的二倍;
哈希函数:
通过hash函数(优质因子31循环累加)先拿到key的hashcode,是一个32位的值,然后让hashcode的高16位和低16位进行异或操作。该函数也称为扰动函数,做到尽可能降低hash碰撞,通过尾插法进行插入。
容量为什么始终都是2^N:
先做对数组的⻓度取模运算,得到的余数才能⽤来要存放的位置也就是对应的数组下标。这个数组下标的计算⽅法是“ (n - 1) & hash ”。(n代表数组⻓度)。方便数组的扩容和增删改时的取模。
JDK1.7与1.8的区别:
JDK1.7 HashMap:
底层是 数组和链表 结合在⼀起使⽤也就是链表散列。如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。扩容翻转时顺序不一致使用头插法会产生死循环,导致cpu100%
JDK1.8 HashMap:
底层数据结构上采用了数组+链表+红黑树;当链表⻓度⼤于阈值(默认为 8-泊松分布),数组的⻓度大于 64时,链表将转化为红⿊树,以减少搜索时间。(解决了tomcat臭名昭著的url参数dos攻击问题)

  • ConcurrentHashMap 

可以通过ConcurrentHashMapHashtable来实现线程安全;Hashtable 是原始API类,通过synchronize同步修饰,效率低下;ConcurrentHashMap通过分段锁实现,效率较比Hashtable要好。
ConcurrentHashMap的底层实现:
JDK1.7的ConcurrentHashMap底层采⽤ 分段的数组+链表 实现;采用 分段锁(Sagment) 对整个桶数组进⾏了分割分段(Segment默认16个),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。

JDK1.8的 ConcurrentHashMap采⽤的数据结构跟HashMap1.8的结构⼀样,数组+链表/红⿊树;摒弃了Segment的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,通过并发控制synchronizedCAS来操作保证线程的安全。

  • 序列化和反序列化


序列化的意思就是将对象的状态转化成字节流,以后可以通过这些值再生成相同状态的对象。对象序列化是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输。反序列化就是根据这些保存的信息重建对象的过程。

序列化:将java对象转化为字节序列的过程。
反序列化:将字节序列转化为java对象的过程。 
优点:
  1. 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里)Redis的RDB
  2. 利用序列化实现远程通信,即在网络上传送对象的字节序列。Google的protoBuf。
反序列化失败的场景:
序列化ID:serialVersionUID不一致的时候,导致反序列化失败。

  • String


String使用数组存储内容,数组使用final修饰,因此String定义的字符串的值也是不可变的。

StringBuffer对方法加了同步锁,线程安全,效率略低于StringBuilder。


  设计模式与原则


  • 单例模式


某个类只能生成一个实例,该实例全局访问,例如Spring容器里一级缓存里的单例池。
优点:
唯一访问:如生成唯一序列化的场景、或者spring默认的bean类型。
提高性能:频繁实例化创建销毁或者耗时耗资源的场景,如连接池、线程池。
缺点:
不适合有状态且需变更的;
实现方式:
饿汉式:线程安全速度快;
懒汉式:双重检测锁,第一次减少锁的开销、第二次防止重复、volatile防止重排序导致实例化未完成;
静态内部类:线程安全利用率高;
枚举:effictiveJAVA推荐,反射也无法破坏;

  • 工厂模式

定义一个用于创建产品的接口,由子类决定生产何种产品。
优点:解耦:提供参数即可获取产品,通过配置文件可以不修改代码增加具体产品。
缺点:每增加一个产品就得新增一个产品类。

  • 抽象工厂模式


提供一个接口,用于创建相关或者依赖对象的家族,并由此进行约束。

优点:可以在类的内部对产品族进行约束。
缺点:假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。


   
   
   
   
   
面试题

  构造方法


构造方法可以被重载,只有当类中没有显性声明任何构造方法时,才会有默认构造方法。

构造方法没有返回值,构造方法的作用是创建新对象。


  初始化块


静态初始化块的优先级最高,会最先执行,在非静态初始化块之前执行。

静态初始化块会在类第一次被加载时最先执行,因此在main方法之前。


  This


关键字this代表当前对象的引用。当前对象指的是调用类中的属性或方法的对象。

关键字this不可以在静态方法中使用。静态方法不依赖于类的具体对象的引用。


  重写和重载的区别


重载指在同一个类中定义多个方法,这些方法名称相同,签名不同。

重写指在子类中的方法的名称和签名都和父类相同,使用override注解。


  Object类方法


toString默认是个指针,一般需要重写;

equals比较对象是否相同,默认和==功能一致;

hashCode散列码,equals则hashCode相同,所以重写equals必须重写hashCode;

finalize用于垃圾回收之前做的遗嘱,默认空,子类需重写;

clone深拷贝,类需实现cloneable的接口;

getClass反射获取对象元数据,包括类名、方法;

notify、wait用于线程通知和唤醒;


▐  基本数据类型和包装类



¤  拓展阅读  ¤

3DXR技术 |  终端技术 |  音视频技术

服务端技术 | 技术质量 | 数据算法


本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

90后程序员开发视频搬运软件、不到一年获利超 700 万,结局很刑! 高中生自创开源编程语言作为成人礼——网友锐评:依托答辩 RustDesk 由于诈骗猖獗,暂停国内服务 淘宝 (taobao.com) 重启网页版优化工作 Java 17 是最常用的 Java LTS 版本 Windows 10 市场份额达 70%,Windows 11 持续下滑 开源日报 | 谷歌扶持鸿蒙上位;开源Rabbit R1;Docker加持的安卓手机;微软的焦虑和野心;海尔电器把开放平台关了 Apple 发布 M4 芯片 谷歌删除 Android 通用内核 (ACK) 对 RISC-V 架构的支持 云风从阿里离职,未来计划制作 Windows 平台的独立游戏
{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4662964/blog/11104131