জাভা থেকে কোটলিন কোড কল করা হচ্ছে

এই কোডল্যাবে, আপনি শিখবেন কিভাবে আপনার Kotlin কোড লিখতে বা মানিয়ে নিতে হয় যাতে এটিকে জাভা কোড থেকে আরও নির্বিঘ্নে কল করা যায়।

আপনি কি শিখবেন

  • কিভাবে @JvmField , @JvmStatic এবং অন্যান্য টীকা ব্যবহার করবেন।
  • জাভা কোড থেকে কিছু কোটলিন ভাষার বৈশিষ্ট্য অ্যাক্সেসের সীমাবদ্ধতা।

আপনি ইতিমধ্যে কি জানতে হবে

এই কোডল্যাবটি প্রোগ্রামারদের জন্য লিখিত এবং প্রাথমিক জাভা এবং কোটলিন জ্ঞান অনুমান করে।

এই কোডল্যাবটি জাভা প্রোগ্রামিং ভাষার সাথে লেখা একটি বৃহত্তর প্রকল্পের স্থানান্তরিত অংশকে অনুকরণ করে, নতুন কোটলিন কোড অন্তর্ভুক্ত করার জন্য।

জিনিসগুলিকে সহজ করার জন্য, আমাদের কাছে UseCase.java .java থাকবে, যা বিদ্যমান কোডবেসকে উপস্থাপন করবে।

আমরা কল্পনা করব যে আমরা জাভাতে লেখা কিছু কার্যকারিতা কোটলিনে লেখা একটি নতুন সংস্করণ দিয়ে প্রতিস্থাপন করেছি এবং আমাদের এটিকে একীভূত করা শেষ করতে হবে।

প্রকল্পটি আমদানি করুন

প্রকল্পের কোড এখানে গিটহাব প্রকল্প থেকে ক্লোন করা যেতে পারে: GitHub

বিকল্পভাবে, আপনি এখানে পাওয়া একটি জিপ সংরক্ষণাগার থেকে প্রকল্পটি ডাউনলোড এবং নিষ্কাশন করতে পারেন:

জিপ ডাউনলোড করুন

আপনি যদি IntelliJ IDEA ব্যবহার করেন তবে "ইমপোর্ট প্রজেক্ট" নির্বাচন করুন।

আপনি যদি অ্যান্ড্রয়েড স্টুডিও ব্যবহার করেন তবে "ইমপোর্ট প্রজেক্ট (গ্রেডল, ইক্লিপস এডিটি, ইত্যাদি)" নির্বাচন করুন।

আসুন UseCase.java এবং আমরা যে ত্রুটিগুলি দেখতে পাচ্ছি তার মাধ্যমে কাজ শুরু করি।

একটি সমস্যা সহ প্রথম ফাংশন হল registerGuest :

public static User registerGuest(String name) {
   User guest = new User(Repository.getNextGuestId(), StringUtils.nameToLogin(name), name);
   Repository.addUser(guest);
   return guest;
}

Repository.getNextGuestId() এবং Repository.addUser(...) উভয়ের ত্রুটিই একই: "অ-স্ট্যাটিক একটি স্ট্যাটিক প্রসঙ্গ থেকে অ্যাক্সেস করা যাবে না।"

এখন কোটলিন ফাইলগুলির একটির দিকে নজর দেওয়া যাক। Repository.kt ফাইলটি খুলুন।

আমরা দেখতে পাই যে আমাদের রিপোজিটরি একটি সিঙ্গেলটন যা অবজেক্ট কীওয়ার্ড ব্যবহার করে ঘোষণা করা হয়। সমস্যা হল যে কোটলিন স্ট্যাটিক বৈশিষ্ট্য এবং পদ্ধতি হিসাবে এগুলিকে প্রকাশ করার পরিবর্তে আমাদের ক্লাসের মধ্যে একটি স্ট্যাটিক উদাহরণ তৈরি করছে।

উদাহরণস্বরূপ, Repository.getNextGuestId() Repository.INSTANCE.getNextGuestId() করে রেফারেন্স করা যেতে পারে, কিন্তু একটি ভাল উপায় আছে।

আমরা @JvmStatic-এর সাথে রিপোজিটরির পাবলিক প্রোপার্টি এবং পদ্ধতিগুলি টীকা করে স্ট্যাটিক পদ্ধতি এবং বৈশিষ্ট্য তৈরি করতে @JvmStatic :

object Repository {
   val BACKUP_PATH = "/backup/user.repo"

   private val _users = mutableListOf<User>()
   private var _nextGuestId = 1000

   @JvmStatic
   val users: List<User>
       get() = _users

   @JvmStatic
   val nextGuestId
       get() = _nextGuestId++

   init {
       _users.add(User(100, "josh", "Joshua Calvert", listOf("admin", "staff", "sys")))
       _users.add(User(101, "dahybi", "Dahybi Yadev", listOf("staff", "nodes")))
       _users.add(User(102, "sarha", "Sarha Mitcham", listOf("admin", "staff", "sys")))
       _users.add(User(103, "warlow", groups = listOf("staff", "inactive")))
   }

   @JvmStatic
   fun saveAs(path: String?):Boolean {
       val backupPath = path ?: return false

       val outputFile = File(backupPath)
       if (!outputFile.canWrite()) {
           throw FileNotFoundException("Could not write to file: $backupPath")
       }
       // Write data...
       return true
   }

   @JvmStatic
   fun addUser(user: User) {
       // Ensure the user isn't already in the collection.
       val existingUser = users.find { user.id == it.id }
       existingUser?.let { _users.remove(it) }
       // Add the user.
       _users.add(user)
   }
}

আপনার IDE ব্যবহার করে আপনার কোডে @JvmStatic টীকা যোগ করুন।

যদি আমরা UseCase.java এ ফিরে যাই, Repository এর বৈশিষ্ট্য এবং পদ্ধতিগুলি আর ত্রুটি ঘটায় না, Repository.BACKUP_PATH ব্যতীত। আমরা যে পরে ফিরে আসব.

আপাতত, registerGuest() পদ্ধতিতে পরবর্তী ত্রুটিটি ঠিক করা যাক।

আসুন নিম্নলিখিত পরিস্থিতি বিবেচনা করি: স্ট্রিং অপারেশনের জন্য আমাদের বেশ কয়েকটি স্ট্যাটিক ফাংশন সহ একটি StringUtils ক্লাস ছিল। যখন আমরা এটিকে কোটলিনে রূপান্তর করি, তখন আমরা পদ্ধতিগুলিকে এক্সটেনশন ফাংশনে রূপান্তর করি। জাভাতে এক্সটেনশন ফাংশন নেই, তাই কোটলিন এই পদ্ধতিগুলিকে স্ট্যাটিক ফাংশন হিসাবে কম্পাইল করে।

দুর্ভাগ্যবশত, যদি আমরা UseCase.java এর ভিতরে registerGuest() পদ্ধতির দিকে তাকাই, আমরা দেখতে পাব যে কিছু ঠিক নয়:

User guest = new User(Repository.getNextGuestId(), StringUtils.nameToLogin(name), name);

কারণ হল যে কোটলিন এই "টপ-লেভেল" বা প্যাকেজ-লেভেল ফাংশনগুলিকে একটি ক্লাসের ভিতরে রাখে যার নাম ফাইলের নামের উপর ভিত্তি করে। এই ক্ষেত্রে, যেহেতু ফাইলটির নাম StringUtils.kt, সংশ্লিষ্ট ক্লাসটির নাম StringUtilsKt

আমরা StringUtils এর আমাদের সমস্ত রেফারেন্স StringUtils এ পরিবর্তন করতে StringUtilsKt এবং এই ত্রুটিটি ঠিক করতে পারি, কিন্তু এটি আদর্শ নয় কারণ:

  • আমাদের কোডে এমন অনেক জায়গা থাকতে পারে যা আপডেট করতে হবে।
  • নামটি নিজেই বিশ্রী।

তাই আমাদের জাভা কোড রিফ্যাক্টর করার পরিবর্তে, এই পদ্ধতিগুলির জন্য একটি ভিন্ন নাম ব্যবহার করতে আমাদের কোটলিন কোড আপডেট করা যাক।

StringUtils.Kt খুলুন, এবং নিম্নলিখিত প্যাকেজ ঘোষণা খুঁজুন:

package com.google.example.javafriendlykotlin

@file:JvmName টীকা ব্যবহার করে আমরা কোটলিনকে প্যাকেজ-স্তরের পদ্ধতির জন্য একটি ভিন্ন নাম ব্যবহার করতে বলতে পারি। StringUtils ক্লাসের নাম দিতে এই টীকাটি ব্যবহার করা যাক।

@file:JvmName("StringUtils")

package com.google.example.javafriendlykotlin

এখন, যদি আমরা UseCase.java এর দিকে ফিরে তাকাই, আমরা দেখতে পাব যে StringUtils.nameToLogin() এর ত্রুটিটি সমাধান করা হয়েছে।

দুর্ভাগ্যবশত, User জন্য কনস্ট্রাক্টরে প্যারামিটারগুলি পাস করার বিষয়ে এই ত্রুটিটিকে একটি নতুন দিয়ে প্রতিস্থাপিত করা হয়েছে। চলুন পরবর্তী ধাপে যাওয়া যাক এবং UseCase.registerGuest() এ এই শেষ ত্রুটিটি ঠিক করুন।

কোটলিন প্যারামিটারের জন্য ডিফল্ট মান সমর্থন করে। Repository.kt এর init ব্লকের ভিতরে দেখে আমরা দেখতে পারি সেগুলি কীভাবে ব্যবহার করা হয়।

Repository.kt:

_users.add(User(102, "sarha", "Sarha Mitcham", listOf("admin", "staff", "sys")))
_users.add(User(103, "warlow", groups = listOf("staff", "inactive")))

আমরা দেখতে পাচ্ছি যে "warlow" ব্যবহারকারীর জন্য, আমরা displayName-এর জন্য একটি মান রাখা এড়িয়ে যেতে পারি কারণ displayNameUser.kt জন্য একটি ডিফল্ট মান নির্দিষ্ট করা আছে।

User.kt:

data class User(
   val id: Int,
   val username: String,
   val displayName: String = username.toTitleCase(),
   val groups: List<String> = listOf("guest")
)

দুর্ভাগ্যবশত জাভা থেকে পদ্ধতিটি কল করার সময় এটি একই কাজ করে না।

UseCase.java:

User guest = new User(Repository.getNextGuestId(), StringUtils.nameToLogin(name), name);

ডিফল্ট মান জাভা প্রোগ্রামিং ভাষায় সমর্থিত নয়। এটি ঠিক করার জন্য, আসুন কোটলিনকে বলি @JvmOverloads টীকাটির সাহায্যে আমাদের কনস্ট্রাক্টরের জন্য ওভারলোড তৈরি করতে।

প্রথমত, আমাদের User.kt এ সামান্য আপডেট করতে হবে।

যেহেতু User ক্লাসে শুধুমাত্র একটি একক, প্রাথমিক কনস্ট্রাক্টর আছে এবং কনস্ট্রাক্টর কোনো টীকা অন্তর্ভুক্ত করে না, constructor কীওয়ার্ডটি বাদ দেওয়া হয়েছে। এখন যেহেতু আমরা এটি টীকা করতে চাই, তবে, constructor কীওয়ার্ড অবশ্যই অন্তর্ভুক্ত করা উচিত:

data class User constructor(
    val id: Int,
    val username: String,
    val displayName: String = username.toTitleCase(),
    val groups: List<String> = listOf("guest")
)

বর্তমান constructor কীওয়ার্ডের সাথে, আমরা @JvmOverloads টীকা যোগ করতে পারি:

data class User @JvmOverloads constructor(
    val id: Int,
    val username: String,
    val displayName: String = username.toTitleCase(),
    val groups: List<String> = listOf("guest")
)

যদি আমরা UseCase.java ফিরে যাই, তাহলে আমরা দেখতে পাব যে registerGuest ফাংশনে আর কোনো ত্রুটি নেই!

আমাদের পরবর্তী ধাপ হল user.hasSystemAccess() -এ UseCase.getSystemUsers() কে ভাঙা কল ঠিক করা। এর জন্য পরবর্তী ধাপে এগিয়ে যান, অথবা @JvmOverloads ত্রুটিটি ঠিক করার জন্য যা করেছে তা গভীরভাবে খনন করতে পড়া চালিয়ে যান।

@জেভিএমওভারলোডস

@JvmOverloads কী করে তা আরও ভালভাবে বোঝার জন্য, আসুন UseCase.java এ একটি পরীক্ষা পদ্ধতি তৈরি করি:

private void testJvmOverloads() {
   User syrinx = new User(1001, "syrinx");
   User ione = new User(1002, "ione", "Ione Saldana");

   List<String> groups = new ArrayList<>();
   groups.add("staff");
   User beaulieu = new User(1002, "beaulieu", groups);
}

আমরা মাত্র দুটি প্যারামিটার, id এবং username দিয়ে একটি User তৈরি করতে পারি:

User syrinx = new User(1001, "syrinx");

groups জন্য ডিফল্ট মান ব্যবহার করার সময় আমরা displayName এর জন্য একটি তৃতীয় প্যারামিটার অন্তর্ভুক্ত করে একটি User তৈরি করতে পারি:

User ione = new User(1002, "ione", "Ione Saldana");

কিন্তু displayName এড়িয়ে যাওয়া এবং অতিরিক্ত কোড না লিখে শুধুমাত্র groups জন্য একটি মান প্রদান করা সম্ভব নয়:

সুতরাং আসুন সেই লাইনটি মুছে ফেলি বা মন্তব্য করার জন্য '//' দিয়ে এটির মুখবন্ধ করি।

কোটলিনে, যদি আমরা ডিফল্ট এবং অ-ডিফল্ট পরামিতিগুলিকে একত্রিত করতে চাই, আমাদের নামযুক্ত পরামিতিগুলি ব্যবহার করতে হবে।

// This doesn't work...
User(104, "warlow", listOf("staff", "inactive"))
// But using named parameters, it does...
User(104, "warlow", groups = listOf("staff", "inactive"))

কারণ হল যে কোটলিন কনস্ট্রাক্টর সহ ফাংশনগুলির জন্য ওভারলোড তৈরি করবে, কিন্তু এটি শুধুমাত্র একটি ডিফল্ট মান সহ প্যারামিটার প্রতি একটি ওভারলোড তৈরি করবে।

আসুন UseCase.java এ ফিরে তাকাই এবং আমাদের পরবর্তী সমস্যার সমাধান করি: user.hasSystemAccess() পদ্ধতিতে UseCase.getSystemUsers() কে কল করুন :

public static List<User> getSystemUsers() {
   ArrayList<User> systemUsers = new ArrayList<>();
   for (User user : Repository.getUsers()) {
       if (user.hasSystemAccess()) {     // Now has an error!
           systemUsers.add(user);
       }
   }
   return systemUsers;
}

এটি একটি আকর্ষণীয় ত্রুটি! আপনি যদি User ক্লাসে আপনার IDE-এর স্বয়ংসম্পূর্ণ বৈশিষ্ট্যটি ব্যবহার করেন, আপনি লক্ষ্য করবেন hasSystemAccess() এর নাম পরিবর্তন করে getHasSystemAccess() করা হয়েছে।

সমস্যা সমাধানের জন্য, আমরা Kotlin val সম্পত্তি hasSystemAccess এর জন্য একটি ভিন্ন নাম তৈরি করতে চাই। এটি করার জন্য, আমরা @JvmName টীকা ব্যবহার করতে পারি। আসুন User.kt এ ফিরে যাই এবং দেখি কোথায় এটি প্রয়োগ করা উচিত।

আমরা টীকা প্রয়োগ করতে পারেন দুটি উপায় আছে. প্রথমটি হল এটি সরাসরি get() পদ্ধতিতে প্রয়োগ করা, যেমন:

val hasSystemAccess
   @JvmName("hasSystemAccess")
   get() = "sys" in groups

এটি কোটলিনকে প্রদত্ত নামে স্পষ্টভাবে সংজ্ঞায়িত গেটারের স্বাক্ষর পরিবর্তন করার জন্য সংকেত দেয়।

বিকল্পভাবে, এটির মতো একটি get: উপসর্গ ব্যবহার করে সম্পত্তিতে এটি প্রয়োগ করা সম্ভব:

@get:JvmName("hasSystemAccess")
val hasSystemAccess
   get() = "sys" in groups

বিকল্প পদ্ধতিটি এমন বৈশিষ্ট্যগুলির জন্য বিশেষভাবে কার্যকর যা একটি ডিফল্ট, অন্তর্নিহিত-সংজ্ঞায়িত গেটার ব্যবহার করছে। উদাহরণ স্বরূপ:

@get:JvmName("isActive")
val active: Boolean

এটি একটি গেটারকে স্পষ্টভাবে সংজ্ঞায়িত না করেই গেটারের নাম পরিবর্তন করার অনুমতি দেয়।

এই পার্থক্য সত্ত্বেও, আপনি যেটি আপনার কাছে ভাল মনে করেন তা ব্যবহার করতে পারেন। উভয়ের কারণে hasSystemAccess() নামের একটি গেটার তৈরি করবে।

যদি আমরা UseCase.java ফিরে যাই, তাহলে আমরা যাচাই করতে পারি যে getSystemUsers() এখন ত্রুটিমুক্ত!

পরবর্তী ত্রুটিটি হল formatUser() , কিন্তু আপনি যদি Kotlin getter নামকরণ কনভেনশন সম্পর্কে আরও পড়তে চান, তাহলে পরবর্তী ধাপে যাওয়ার আগে এখানে পড়া চালিয়ে যান।

গেটার এবং সেটার নামকরণ

যখন আমরা কোটলিন লিখি, তখন লেখার কোডটি ভুলে যাওয়া সহজ যেমন:

val myString = "Logged in as ${user.displayName}")

আসলে displayName এর মান পেতে একটি ফাংশন কল করছে। আমরা মেনুতে Tools > Kotlin > Show Kotlin Bytecode- এ গিয়ে ডিকম্পাইল বোতামে ক্লিক করে এটি যাচাই করতে পারি:

String myString = "Logged in as " + user.getDisplayName();

যখন আমরা জাভা থেকে এগুলি অ্যাক্সেস করতে চাই, তখন আমাদের স্পষ্টভাবে গেটারের নাম লিখতে হবে।

বেশিরভাগ ক্ষেত্রে, Kotlin বৈশিষ্ট্যের জন্য গেটারদের জাভা নামটি হল get + প্রপার্টির নাম, যেমনটি আমরা User.getHasSystemAccess() এবং User.getDisplayName() দিয়ে দেখেছি। একটি ব্যতিক্রম হল বৈশিষ্ট্য যার নাম "is" দিয়ে শুরু হয়। এই ক্ষেত্রে, গেটারের জন্য জাভা নামটি কোটলিন সম্পত্তির নাম।

উদাহরণস্বরূপ, User একটি সম্পত্তি যেমন:

val isAdmin get() = //...

এর সাথে জাভা থেকে অ্যাক্সেস করা হবে:

boolean userIsAnAdmin = user.isAdmin();

@JvmName টীকাটি ব্যবহার করে, Kotlin বাইটকোড তৈরি করে যেটি আইটেমটি টীকা করার জন্য ডিফল্টের পরিবর্তে নির্দিষ্ট নাম রয়েছে।

এটি সেটারের জন্য একই কাজ করে, যাদের তৈরি করা নামগুলি সর্বদা set হয় + সম্পত্তির নাম। উদাহরণস্বরূপ, নিম্নলিখিত ক্লাস নিন:

class Color {
   var red = 0f
   var green = 0f
   var blue = 0f
}

ধরা যাক আমরা সেটারের নাম setRed() তে পরিবর্তন করতে চাই, যখন updateRed() একা রেখে। আমরা এটি করতে @set:JvmName সংস্করণটি ব্যবহার করতে পারি:

class Color {
   @set:JvmName("updateRed")
   var red = 0f
   @set:JvmName("updateGreen")
   var green = 0f
   @set:JvmName("updateBlue")
   var blue = 0f
}

জাভা থেকে, আমরা তখন লিখতে সক্ষম হব:

color.updateRed(0.8f);

UseCase.formatUser() একটি User অবজেক্টের বৈশিষ্ট্যের মান পেতে সরাসরি ক্ষেত্র অ্যাক্সেস ব্যবহার করে।

কোটলিনে, বৈশিষ্ট্যগুলি সাধারণত গেটার এবং সেটারের মাধ্যমে প্রকাশ করা হয়। এই val বৈশিষ্ট্য অন্তর্ভুক্ত.

@JvmField টীকা ব্যবহার করে এই আচরণটি পরিবর্তন করা সম্ভব। যখন এটি একটি ক্লাসের একটি সম্পত্তিতে প্রয়োগ করা হয়, তখন কোটলিন গেটার (এবং var বৈশিষ্ট্যের জন্য সেটার) পদ্ধতিগুলি এড়িয়ে যাবে এবং ব্যাকিং ক্ষেত্রটি সরাসরি অ্যাক্সেস করা যেতে পারে।

যেহেতু User বস্তু অপরিবর্তনীয়, তাই আমরা তাদের প্রতিটি বৈশিষ্ট্যকে ক্ষেত্র হিসাবে প্রকাশ করতে চাই, এবং তাই আমরা তাদের @JvmField দিয়ে টীকা করব:

data class User @JvmOverloads constructor(
   @JvmField val id: Int,
   @JvmField val username: String,
   @JvmField val displayName: String = username.toTitleCase(),
   @JvmField val groups: List<String> = listOf("guest")
) {
   @get:JvmName("hasSystemAccess")
   val hasSystemAccess
       get() = "sys" in groups
}

আমরা যদি UseCase.formatUser() এ এখন ফিরে তাকাই তবে আমরা দেখতে পাব যে ত্রুটিগুলি সংশোধন করা হয়েছে!

@JvmField বা const

এর সাথে, UseCase.java ফাইলে আরেকটি অনুরূপ ত্রুটি রয়েছে:

Repository.saveAs(Repository.BACKUP_PATH);

যদি আমরা এখানে স্বয়ংসম্পূর্ণ ব্যবহার করি, তাহলে আমরা দেখতে পাব যে একটি Repository.getBACKUP_PATH() আছে, এবং তাই এটি BACKUP_PATH-এ BACKUP_PATH থেকে @JvmStatic @JvmField এ টীকা পরিবর্তন করতে প্রলুব্ধ হতে পারে।

এর চেষ্টা করা যাক. Repository.kt এ ফিরে যান এবং টীকা আপডেট করুন:

object Repository {
   @JvmField
   val BACKUP_PATH = "/backup/user.repo"

আমরা যদি UseCase.java এখন দেখি, আমরা দেখতে পাব যে ত্রুটিটি চলে গেছে, কিন্তু BACKUP_PATH এ একটি নোটও রয়েছে:

কোটলিনে, একমাত্র ধরনগুলি যেগুলি const হতে পারে তা হল আদিম, যেমন int , float এবং String । এই ক্ষেত্রে, কারণ BACKUP_PATH একটি স্ট্রিং, আমরা একটি ক্ষেত্র হিসাবে মানটি অ্যাক্সেস করার ক্ষমতা বজায় রেখে @JvmField এর সাথে টীকাযুক্ত একটি val পরিবর্তে const val ব্যবহার করে আরও ভাল পারফরম্যান্স পেতে পারি।

এখন Repository.kt এ পরিবর্তন করা যাক:

object Repository {
   const val BACKUP_PATH = "/backup/user.repo"

যদি আমরা UseCase.java এ ফিরে তাকাই তাহলে আমরা দেখতে পাব যে শুধুমাত্র একটি ত্রুটি বাকি আছে।

চূড়ান্ত ত্রুটি Exception: 'java.io.IOException' is never thrown in the corresponding try block.

আমরা যদি Repository.saveAsRepository.kt এর কোড দেখি, তবে আমরা দেখতে পাই যে এটি একটি ব্যতিক্রম নিক্ষেপ করে। কি হচ্ছে?

জাভা একটি "চেক করা ব্যতিক্রম" ধারণা আছে। এগুলি হল ব্যতিক্রম যেগুলি থেকে পুনরুদ্ধার করা যেতে পারে, যেমন ব্যবহারকারী একটি ফাইলের নাম ভুল টাইপ করছেন, বা নেটওয়ার্ক সাময়িকভাবে অনুপলব্ধ। একটি চেক করা ব্যতিক্রম ধরা পড়ার পরে, বিকাশকারী তারপরে কীভাবে সমস্যাটি সমাধান করবেন সে সম্পর্কে ব্যবহারকারীকে প্রতিক্রিয়া প্রদান করতে পারে।

যেহেতু চেক করা ব্যতিক্রমগুলি কম্পাইলের সময় চেক করা হয়, আপনি সেগুলিকে পদ্ধতির স্বাক্ষরে ঘোষণা করেন:

public void openFile(File file) throws FileNotFoundException {
   // ...
}

অন্যদিকে, কোটলিনের চেক করা ব্যতিক্রম নেই, এবং এটিই এখানে সমস্যা সৃষ্টি করছে।

সমাধান হল IOException যোগ করতে বলা যা সম্ভাব্যভাবে Repository.saveAs() এর স্বাক্ষরে নিক্ষেপ করা হয়েছে, যাতে JVM বাইটকোড এটিকে চেক করা ব্যতিক্রম হিসেবে অন্তর্ভুক্ত করে।

আমরা এটি @Throws টীকা দিয়ে করি, যা জাভা/কোটলিন আন্তঃঅপারেবিলিটির সাথে সাহায্য করে। কোটলিনে, ব্যতিক্রমগুলি জাভার মতোই আচরণ করে, কিন্তু জাভা থেকে ভিন্ন, কোটলিনে শুধুমাত্র অচেক করা ব্যতিক্রম রয়েছে। সুতরাং আপনি যদি আপনার জাভা কোডকে জানাতে চান যে একটি Kotlin ফাংশন একটি ব্যতিক্রম ছুঁড়েছে, তাহলে আপনাকে @Throws টীকা ব্যবহার করতে হবে Kotlin ফাংশন স্বাক্ষরে স্যুইচ করুন Repository.kt file যান এবং নতুন টীকা অন্তর্ভুক্ত করতে saveAs() আপডেট করুন:

@JvmStatic
@Throws(IOException::class)
fun saveAs(path: String?) {
   val outputFile = File(path)
   if (!outputFile.canWrite()) {
       throw FileNotFoundException("Could not write to file: $path")
   }
   // Write data...
}

@Throws টীকাটি জায়গায় রেখে, আমরা দেখতে পাচ্ছি যে UseCase.java এর সমস্ত কম্পাইলার ত্রুটি সংশোধন করা হয়েছে! হুররে!

আপনি হয়তো ভাবতে পারেন যে এখন Kotlin থেকে saveAs() কল করার সময় আপনাকে try অ্যান্ড catch ব্লক ব্যবহার করতে হবে।

না! মনে রাখবেন, কোটলিনের চেক করা ব্যতিক্রম নেই, এবং @Throws একটি পদ্ধতিতে যোগ করলে তা পরিবর্তন হয় না:

fun saveFromKotlin(path: String) {
   Repository.saveAs(path)
}

যখন সেগুলি পরিচালনা করা যায় তখন ব্যতিক্রমগুলি ধরার জন্য এটি এখনও কার্যকর, তবে কোটলিন আপনাকে সেগুলি পরিচালনা করতে বাধ্য করে না।

এই কোডল্যাবে, আমরা কোটলিন কোড কীভাবে লিখতে হয় তার প্রাথমিক বিষয়গুলি কভার করেছি যা ইডিওমেটিক জাভা কোড লেখাকে সমর্থন করে।

কোটলিন তার JVM বাইটকোড তৈরি করার উপায় পরিবর্তন করতে আমরা কীভাবে টীকা ব্যবহার করতে পারি সে সম্পর্কে আমরা কথা বলেছি, যেমন:

  • @JvmStatic স্ট্যাটিক সদস্য এবং পদ্ধতি তৈরি করতে।
  • ডিফল্ট মান আছে এমন ফাংশনগুলির জন্য ওভারলোড করা পদ্ধতি তৈরি করতে @JvmOverloads
  • গেটার এবং সেটারের নাম পরিবর্তন করতে @JvmName
  • @JvmField এবং সেটারের মাধ্যমে না হয়ে সরাসরি একটি ক্ষেত্র হিসাবে একটি সম্পত্তি প্রকাশ করতে।
  • @Throws চেক করা ব্যতিক্রম ঘোষণা করতে।

আমাদের ফাইলের চূড়ান্ত বিষয়বস্তু হল:

User.kt

data class User @JvmOverloads constructor(
   @JvmField val id: Int,
   @JvmField val username: String,
   @JvmField val displayName: String = username.toTitleCase(),
   @JvmField val groups: List<String> = listOf("guest")
) {
   val hasSystemAccess
       @JvmName("hasSystemAccess")
       get() = "sys" in groups
}

Repository.kt

object Repository {
   const val BACKUP_PATH = "/backup/user.repo"

   private val _users = mutableListOf<User>()
   private var _nextGuestId = 1000

   @JvmStatic
   val users: List<User>
       get() = _users

   @JvmStatic
   val nextGuestId
       get() = _nextGuestId++

   init {
       _users.add(User(100, "josh", "Joshua Calvert", listOf("admin", "staff", "sys")))
       _users.add(User(101, "dahybi", "Dahybi Yadev", listOf("staff", "nodes")))
       _users.add(User(102, "sarha", "Sarha Mitcham", listOf("admin", "staff", "sys")))
       _users.add(User(103, "warlow", groups = listOf("staff", "inactive")))
   }

   @JvmStatic
   @Throws(IOException::class)
   fun saveAs(path: String?):Boolean {
       val backupPath = path ?: return false

       val outputFile = File(backupPath)
       if (!outputFile.canWrite()) {
           throw FileNotFoundException("Could not write to file: $backupPath")
       }
       // Write data...
       return true
   }

   @JvmStatic
   fun addUser(user: User) {
       // Ensure the user isn't already in the collection.
       val existingUser = users.find { user.id == it.id }
       existingUser?.let { _users.remove(it) }
       // Add the user.
       _users.add(user)
   }
}

StringUtils.kt

@file:JvmName("StringUtils")

package com.google.example.javafriendlykotlin

fun String.toTitleCase(): String {
   if (isNullOrBlank()) {
       return this
   }

   return split(" ").map { word ->
       word.foldIndexed("") { index, working, char ->
           val nextChar = if (index == 0) char.toUpperCase() else char.toLowerCase()
           "$working$nextChar"
       }
   }.reduceIndexed { index, working, word ->
       if (index > 0) "$working $word" else word
   }
}

fun String.nameToLogin(): String {
   if (isNullOrBlank()) {
       return this
   }
   var working = ""
   toCharArray().forEach { char ->
       if (char.isLetterOrDigit()) {
           working += char.toLowerCase()
       } else if (char.isWhitespace() and !working.endsWith(".")) {
           working += "."
       }
   }
   return working
}